diff options
Diffstat (limited to 'html/plains/game.js')
-rw-r--r-- | html/plains/game.js | 186 |
1 files changed, 184 insertions, 2 deletions
diff --git a/html/plains/game.js b/html/plains/game.js index 57143cd..ad7b8c3 100644 --- a/html/plains/game.js +++ b/html/plains/game.js @@ -13,6 +13,101 @@ const worldToGrid = (x, y) => ({ y: Math.floor(y / CONFIG.display.grid.size) }); +// Add the villager-related functions here +const generateVillagers = () => { + const villagers = []; + const occupiedCells = new Set(); + const gridSize = CONFIG.display.grid.size; + const villageSize = CONFIG.world.village.size; + const worldSize = CONFIG.display.grid.worldSize; + + // First, place one villager near the village + const nearVillageX = villageSize + Math.floor(Math.random() * 2); + const nearVillageY = villageSize + Math.floor(Math.random() * 2); + + villagers.push({ + x: (nearVillageX * gridSize) + (gridSize / 2), + y: (nearVillageY * gridSize) + (gridSize / 2), + color: CONFIG.world.villagers.colors[Math.floor(Math.random() * CONFIG.world.villagers.colors.length)], + shape: CONFIG.world.villagers.shapes[Math.floor(Math.random() * CONFIG.world.villagers.shapes.length)], + status: 'lost', + cellX: nearVillageX, + cellY: nearVillageY, + bobSpeed: 0.005 + Math.random() * 0.005, // Random speed for rescued villagers + bobAmplitude: 2 + Math.random() * 2, // Random amplitude for rescued villagers + lostBobSpeed: 0.005 + Math.random() * 0.005, // Random speed for lost villagers + lostBobAmplitude: 2 + Math.random() * 2 // Random amplitude for lost villagers + }); + occupiedCells.add(`${nearVillageX},${nearVillageY}`); + + // Place remaining villagers + while (villagers.length < CONFIG.world.villagers.total) { + const cellX = villageSize + Math.floor(Math.random() * (worldSize - villageSize)); + const cellY = villageSize + Math.floor(Math.random() * (worldSize - villageSize)); + const cellKey = `${cellX},${cellY}`; + + // Skip if cell is occupied or has a tree + if (occupiedCells.has(cellKey) || state.collisionMap.has(cellKey)) { + continue; + } + + villagers.push({ + x: (cellX * gridSize) + (gridSize / 2), + y: (cellY * gridSize) + (gridSize / 2), + color: CONFIG.world.villagers.colors[Math.floor(Math.random() * CONFIG.world.villagers.colors.length)], + shape: CONFIG.world.villagers.shapes[Math.floor(Math.random() * CONFIG.world.villagers.shapes.length)], + status: 'lost', + cellX, + cellY, + bobSpeed: 0.005 + Math.random() * 0.005, // Random speed for rescued villagers + bobAmplitude: 2 + Math.random() * 2, // Random amplitude for rescued villagers + lostBobSpeed: 0.005 + Math.random() * 0.005, // Random speed for lost villagers + lostBobAmplitude: 2 + Math.random() * 2 // Random amplitude for lost villagers + }); + occupiedCells.add(cellKey); + } + + return villagers; +}; + +const drawVillagerShape = (ctx, x, y, shape, size) => { + ctx.beginPath(); + + switch (shape) { + case 'square': + ctx.rect(x - size/2, y - size/2, size, size); + break; + + case 'triangle': + ctx.moveTo(x, y - size/2); + ctx.lineTo(x + size/2, y + size/2); + ctx.lineTo(x - size/2, y + size/2); + break; + + case 'pentagon': + for (let i = 0; i < 5; i++) { + const angle = (i * 2 * Math.PI / 5) - Math.PI/2; + const px = x + Math.cos(angle) * size/2; + const py = y + Math.sin(angle) * size/2; + if (i === 0) ctx.moveTo(px, py); + else ctx.lineTo(px, py); + } + break; + + case 'hexagon': + for (let i = 0; i < 6; i++) { + const angle = (i * 2 * Math.PI / 6); + const px = x + Math.cos(angle) * size/2; + const py = y + Math.sin(angle) * size/2; + if (i === 0) ctx.moveTo(px, py); + else ctx.lineTo(px, py); + } + break; + } + + ctx.closePath(); +}; + // ============= Configuration ============= const CONFIG = { display: { @@ -94,9 +189,24 @@ const CONFIG = { }, world: { village: { - size: 2, + size: 10, groundColor: '#f2f2f2' }, + villagers: { + total: 1000, // Total number of villagers to spawn + colors: [ + '#4B0082', // Indigo + '#483D8B', // DarkSlateBlue + '#6A5ACD', // SlateBlue + '#2F4F4F', // DarkSlateGray + '#363636', // DarkGray + '#4682B4' // SteelBlue + ], + shapes: ['square', 'triangle', 'pentagon', 'hexagon'], + size: 30, // Same as player size + rescueMessage: 'Congratulations! All villagers have been rescued!', + messageDisplayTime: 5000 // How long to show the completion message (ms) + }, wilderness: { groundColor: '#e6ffe6', vegetation: { @@ -214,7 +324,10 @@ const createInitialState = () => ({ targetX: 0, targetY: 0 }, - collisionMap: new Map() + collisionMap: new Map(), + villagers: [], + gameComplete: false, + gameCompleteMessageShown: false }); let state = createInitialState(); @@ -1182,6 +1295,27 @@ const render = () => { } } + // After drawing vegetation but before drawing the player, add: + state.villagers.forEach(villager => { + ctx.save(); + ctx.fillStyle = villager.color; + + // Add slight bobbing motion for rescued villagers + if (villager.status === 'rescued') { + const bobOffset = Math.sin(animationTime * villager.bobSpeed) * villager.bobAmplitude; + drawVillagerShape(ctx, villager.x, villager.y + bobOffset, villager.shape, CONFIG.world.villagers.size); + } else if (villager.status === 'lost') { + // Horizontal bobbing for lost villagers + const lostBobOffset = Math.sin(animationTime * villager.lostBobSpeed) * villager.lostBobAmplitude; + drawVillagerShape(ctx, villager.x + lostBobOffset, villager.y, villager.shape, CONFIG.world.villagers.size); + } else { + drawVillagerShape(ctx, villager.x, villager.y, villager.shape, CONFIG.world.villagers.size); + } + + ctx.fill(); + ctx.restore(); + }); + // Draw player renderPlayer(); @@ -1248,6 +1382,49 @@ const updatePlayer = () => { state.player.lastInputTime = animationTime; } } + + // Check for villager collisions + state.villagers.forEach(villager => { + if (villager.status === 'rescued') return; + + const dx = state.player.x - villager.x; + const dy = state.player.y - villager.y; + const distance = Math.sqrt(dx * dx + dy * dy); + + if (distance < (CONFIG.player.size + CONFIG.world.villagers.size) / 2) { + villager.status = 'rescued'; + + // Assign new position in village when rescued + const villageSize = CONFIG.world.village.size * CONFIG.display.grid.size; + const margin = CONFIG.world.villagers.size; // Keep villagers away from village edges + + villager.x = margin + Math.random() * (villageSize - margin * 2); + villager.y = margin + Math.random() * (villageSize - margin * 2); + + // Check if all villagers are rescued + const allRescued = state.villagers.every(v => v.status === 'rescued'); + if (allRescued && !state.gameComplete) { + state.gameComplete = true; + // Show completion message + const message = document.createElement('div'); + message.textContent = CONFIG.world.villagers.rescueMessage; + message.style.position = 'fixed'; + message.style.top = '50%'; + message.style.left = '50%'; + message.style.transform = 'translate(-50%, -50%)'; + message.style.padding = '20px'; + message.style.backgroundColor = 'rgba(0, 0, 0, 0.8)'; + message.style.color = 'white'; + message.style.borderRadius = '10px'; + message.style.fontSize = '24px'; + document.body.appendChild(message); + + setTimeout(() => { + document.body.removeChild(message); + }, CONFIG.world.villagers.messageDisplayTime); + } + } + }); }; const gameLoop = (currentTime) => { @@ -1291,6 +1468,11 @@ window.addEventListener('keyup', handleKeyUp); window.addEventListener('resize', resizeCanvas); resizeCanvas(); + +// Initialize villagers after collision map is populated +state.villagers = generateVillagers(); + +// Start the game loop requestAnimationFrame(gameLoop); const getDotOpacity = (state, animationTime) => { |