HexagonGame / index.html
awacke1's picture
Update index.html
9ddeb1a verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hexagon Ecosystem Evolution</title>
<style>
body { margin: 0; background-color: #282c34; color: white; font-family: Arial; overflow: hidden; }
#gameContainer { display: flex; height: 100vh; }
#sidebar { width: 300px; padding: 10px; text-align: left; background-color: #1e1e1e; overflow-y: auto; }
canvas { flex-grow: 1; background-color: #1e1e1e; cursor: pointer; height: 100%; }
#timer, #counts, #selection { font-size: 18px; margin: 10px 0; }
button { margin: 3px; padding: 8px; font-size: 14px; cursor: pointer; width: 50px; }
.category { margin-top: 15px; font-weight: bold; }
</style>
</head>
<body>
<div id="gameContainer">
<div id="sidebar">
<div id="timer">Time Left: 5:00</div>
<div id="counts">Counts: Loading...</div>
<div id="selection">
<div class="category">Plants</div>
<div id="plantSelection"></div>
<div class="category">Insects</div>
<div id="insectSelection"></div>
<div class="category">Animals</div>
<div id="animalSelection"></div>
</div>
</div>
<canvas id="gameCanvas"></canvas>
</div>
<script>
const canvas = document.getElementById("gameCanvas");
const ctx = canvas.getContext("2d");
canvas.width = window.innerWidth - 300; // Full width minus sidebar
canvas.height = window.innerHeight; // Full height
const hexSize = 30;
const hexWidth = Math.sqrt(3) * hexSize;
const hexHeight = 2 * hexSize;
const offsetX = hexWidth;
const offsetY = hexHeight * 0.75;
const rows = Math.ceil(canvas.height / offsetY) + 1;
const cols = Math.ceil(canvas.width / offsetX) + 1;
let hexGrid = [];
let timer = 300;
let selectedEntity = null;
const entities = {
plants: ["🌱", "🌿", "🍁", "🌺", "🌸", "🌼", "🌻", "🌾", "🌳", "🌴", "πŸ‚", "πŸƒ", "πŸ‡", "🍊", "🍎", "πŸ‘", "πŸ’", "πŸ“", "🍍", "πŸ₯•"],
insects: ["🐝", "πŸ¦‹", "🐞"],
animals: ["πŸ‡", "🦊", "🦌", "🐦", "🐿️"]
};
let resources = {};
let counts = {};
let animalPairs = new Map();
function initializeEntities() {
Object.keys(entities).forEach(category => {
entities[category].forEach(entity => {
resources[entity] = 5;
counts[entity] = 0;
});
});
}
function generateHexGrid() {
hexGrid = [];
for (let row = 0; row < rows; row++) {
for (let col = 0; col < cols; col++) {
let x = col * offsetX;
let y = row * offsetY + (col % 2 ? offsetY / 2 : 0);
hexGrid.push({ x, y, type: "empty", entities: [], nourishment: 0 });
}
}
}
function selectEntity(entity) {
selectedEntity = entity;
}
function drawHex(hex) {
const { x, y, type, entities } = hex;
ctx.beginPath();
for (let i = 0; i < 6; i++) {
let angle = (Math.PI / 3) * i;
let px = x + hexSize * Math.cos(angle);
let py = y + hexSize * Math.sin(angle);
ctx[i === 0 ? "moveTo" : "lineTo"](px, py);
}
ctx.closePath();
ctx.fillStyle = type === "empty" ? "#C2B280" : "#90EE90";
ctx.fill();
ctx.stroke();
ctx.fillStyle = "white";
ctx.font = "18px Arial";
entities.forEach((entity, index) => {
let randX = x - 8 + (Math.random() * 16 - 8);
let randY = y + 4 + (Math.random() * 16 - 8);
ctx.fillText(entity, randX, randY);
});
}
function getNeighbors(hexIndex) {
const neighbors = [];
const row = Math.floor(hexIndex / cols);
const col = hexIndex % cols;
const directions = [
[-1, 0], [1, 0], [0, -1], [0, 1],
col % 2 ? [-1, -1] : [-1, 1],
col % 2 ? [1, -1] : [1, 1]
];
directions.forEach(([dr, dc]) => {
const newRow = row + dr;
const newCol = col + dc;
if (newRow >= 0 && newRow < rows && newCol >= 0 && newCol < cols) {
neighbors.push(newRow * cols + newCol);
}
});
return neighbors;
}
function aStar(startIdx, goalIdx) {
const openSet = new Set([startIdx]);
const cameFrom = {};
const gScore = { [startIdx]: 0 };
const fScore = { [startIdx]: heuristic(startIdx, goalIdx) };
while (openSet.size > 0) {
let current = Array.from(openSet).reduce((a, b) => fScore[a] < fScore[b] ? a : b);
if (current === goalIdx) return reconstructPath(cameFrom, current);
openSet.delete(current);
getNeighbors(current).forEach(neighbor => {
const tentativeGScore = gScore[current] + 1;
if (!gScore[neighbor] || tentativeGScore < gScore[neighbor]) {
cameFrom[neighbor] = current;
gScore[neighbor] = tentativeGScore;
fScore[neighbor] = gScore[neighbor] + heuristic(neighbor, goalIdx);
openSet.add(neighbor);
}
});
}
return [];
}
function heuristic(a, b) {
const rowA = Math.floor(a / cols), colA = a % cols;
const rowB = Math.floor(b / cols), colB = b % cols;
return Math.abs(rowA - rowB) + Math.abs(colA - colB);
}
function reconstructPath(cameFrom, current) {
const path = [current];
while (cameFrom[current] !== undefined) {
current = cameFrom[current];
path.unshift(current);
}
return path;
}
function pollinate() {
hexGrid.forEach(hex => {
if (hex.entities.some(e => entities.insects.includes(e))) {
hex.entities.forEach(entity => {
if (entities.plants.includes(entity) && Math.random() < 0.2) {
hex.entities.push(entity); // Plant baby in same hex
counts[entity]++;
}
});
}
});
}
function moveAnimals() {
hexGrid.forEach((hex, index) => {
let animalsToMove = [];
hex.entities.forEach((entity, i) => {
if (entities.animals.includes(entity)) {
let nourishment = hex.nourishment;
const hasPlants = hex.entities.some(e => entities.plants.includes(e));
const hasInsects = hex.entities.some(e => entities.insects.includes(e));
// Eating logic
if (hasPlants && Math.random() < 0.3) {
const plantIdx = hex.entities.findIndex(e => entities.plants.includes(e));
hex.entities.splice(plantIdx, 1);
nourishment += 2;
counts[hex.entities[plantIdx]]--;
} else if (hasInsects && Math.random() < 0.5) {
const insectIdx = hex.entities.findIndex(e => entities.insects.includes(e));
hex.entities.splice(insectIdx, 1);
nourishment += 1;
counts[hex.entities[insectIdx]]--;
}
// Reproduction
const mateCount = hex.entities.filter(e => e === entity).length;
if (mateCount >= 2 && nourishment >= 2 && Math.random() < 0.1) {
hex.entities.push(entity); // Baby in same hex
counts[entity]++;
nourishment -= 2;
if (!animalPairs.has(entity + index)) {
animalPairs.set(entity + index, true);
}
}
// Movement toward food
const neighbors = getNeighbors(index);
let goalIdx = neighbors.find(n => hexGrid[n].entities.some(e => entities.plants.includes(e)));
if (!goalIdx) goalIdx = neighbors.find(n => hexGrid[n].entities.some(e => entities.insects.includes(e)));
if (!goalIdx) goalIdx = neighbors[Math.floor(Math.random() * neighbors.length)];
const path = aStar(index, goalIdx);
if (path.length > 1) {
animalsToMove.push({ entity, fromIdx: index, toIdx: path[1], nourishment });
if (animalPairs.has(entity + index)) {
const pairIdx = hex.entities.indexOf(entity, i + 1);
if (pairIdx !== -1) {
animalsToMove.push({ entity, fromIdx: index, toIdx: path[1], nourishment });
}
}
}
}
});
animalsToMove.forEach(move => {
const fromHex = hexGrid[move.fromIdx];
const toHex = hexGrid[move.toIdx];
const entityIdx = fromHex.entities.indexOf(move.entity);
if (entityIdx !== -1) {
fromHex.entities.splice(entityIdx, 1);
toHex.entities.push(move.entity);
toHex.nourishment += move.nourishment;
fromHex.nourishment = Math.max(0, fromHex.nourishment - move.nourishment);
if (fromHex.entities.length === 0) fromHex.type = "empty";
toHex.type = "occupied";
if (animalPairs.has(move.entity + move.fromIdx)) {
animalPairs.delete(move.entity + move.fromIdx);
animalPairs.set(move.entity + move.toIdx, true);
}
}
});
});
}
function renderMap() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
hexGrid.forEach(hex => drawHex(hex));
}
function updateGame() {
timer--;
document.getElementById("timer").innerText = `Time Left: ${Math.floor(timer / 60)}:${(timer % 60).toString().padStart(2, '0')}`;
if (timer % 5 === 0) {
pollinate();
moveAnimals();
updateCounts();
}
renderMap();
if (timer <= 0) {
clearInterval(gameLoop);
alert("Game Over!");
}
}
function updateCounts() {
let countText = "Counts: ";
Object.keys(counts).forEach(entity => {
countText += `${entity}:${counts[entity]} `;
});
document.getElementById("counts").innerText = countText;
}
function populateSidebar() {
["plantSelection", "insectSelection", "animalSelection"].forEach((id, idx) => {
const category = ["plants", "insects", "animals"][idx];
const div = document.getElementById(id);
entities[category].forEach(entity => {
const btn = document.createElement("button");
btn.innerText = entity;
btn.onclick = () => selectEntity(entity);
div.appendChild(btn);
});
});
}
canvas.addEventListener("click", (event) => {
const rect = canvas.getBoundingClientRect();
const mouseX = event.clientX - rect.left;
const mouseY = event.clientY - rect.top;
hexGrid.forEach(hex => {
if (Math.hypot(hex.x - mouseX, hex.y - mouseY) < hexSize) {
if (selectedEntity && resources[selectedEntity] > 0 && hex.type === "empty") {
hex.type = "occupied";
hex.entities.push(selectedEntity);
resources[selectedEntity]--;
counts[selectedEntity]++;
updateCounts();
}
}
});
renderMap();
});
window.addEventListener("resize", () => {
canvas.width = window.innerWidth - 300;
canvas.height = window.innerHeight;
generateHexGrid();
renderMap();
});
initializeEntities();
generateHexGrid();
populateSidebar();
renderMap();
const gameLoop = setInterval(updateGame, 1000);
</script>
</body>
</html>