about summary refs log tree commit diff stats
path: root/html/plains/game.js
diff options
context:
space:
mode:
Diffstat (limited to 'html/plains/game.js')
-rw-r--r--html/plains/game.js186
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) => {