about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--html/plains/config.js163
-rw-r--r--html/plains/game.js186
2 files changed, 184 insertions, 165 deletions
diff --git a/html/plains/config.js b/html/plains/config.js
deleted file mode 100644
index 2f5e227..0000000
--- a/html/plains/config.js
+++ /dev/null
@@ -1,163 +0,0 @@
-export const CONFIG = {
-    display: {
-        fps: 60,
-        grid: {
-            size: 100,
-            color: 'rgba(221, 221, 221, 0.5)',
-            worldSize: 100,
-            voidColor: '#e6f3ff'
-        },
-        camera: {
-            deadzoneMultiplierX: 0.6,
-            deadzoneMultiplierY: 0.6,
-            ease: 0.08
-        }
-    },
-    effects: {
-        colors: {
-            primary: '#4169E1',
-            secondary: '#1E90FF',
-            tertiary: '#0000CD',
-            glow: 'rgba(0, 128, 255, 0.5)',
-            inner: '#0000CD'
-        }
-    },
-    player: {
-        size: 30,
-        speed: 5,
-        sprintMultiplier: 2,
-        color: '#111',
-        strafeKey: ' ',
-        directionIndicator: {
-            size: 10,
-            color: 'rgba(32, 178, 170, 1)'
-        },
-        dash: {
-            duration: 3000,    // 3 seconds of use
-            cooldown: 1000,    // 1 second cooldown
-            exhaustedAt: 0     // Track when dash was exhausted
-        },
-        idle: {
-            startDelay: 1500,    // Start idle animation after 1.5 seconds
-            lookSpeed: 0.001,    // Speed of the looking animation
-            lookRadius: 0.4      // How far to look around (in radians)
-        }
-    },
-    sword: {
-        length: 60,
-        swingSpeed: 0.6,
-        colors: null
-    },
-    bubble: {
-        size: 20,
-        speed: 8,
-        lifetime: 800,
-        cooldown: 1000,
-        arcWidth: Math.PI / 3,
-        colors: null,
-        particleEmitRate: 0.3,
-        fadeExponent: 2.5
-    },
-    bubbleParticle: {
-        lifetime: 700,
-        speedMultiplier: 0.3,
-        size: 3
-    },
-    defense: {
-        numLayers: 6,
-        maxRadiusMultiplier: 2,
-        baseAlpha: 0.15,
-        particleCount: 12,
-        orbitRadiusMultiplier: 0.8,
-        rotationSpeed: 1.5
-    },
-    footprints: {
-        lifetime: 1000,
-        spacing: 300,
-        size: 5
-    },
-    world: {
-        village: {
-            size: 2,
-            groundColor: '#f2f2f2'
-        },
-        wilderness: {
-            groundColor: '#e6ffe6',
-            vegetation: {
-                tree: {
-                    frequency: 0.1,  // Chance per grid cell
-                    colors: [
-                        'rgba(100, 144, 79, 1)',
-                        'rgba(85, 128, 64, 1)',
-                        'rgba(128, 164, 98, 1)',
-                        'rgba(110, 139, 61, 1)',
-                        'rgba(95, 133, 73, 1)',
-                        'rgba(248, 239, 58, 1)'
-                    ],
-                    size: { min: 20, max: 30 }
-                },
-                mushroom: {
-                    frequency: 0.03,
-                    colors: [
-                        'rgba(242, 63, 63, 0.25)',
-                        'rgba(245, 131, 148, 0.25)',
-                        'rgba(255, 119, 65, 0.25)',
-                        'rgba(193, 97, 1, 0.5)'
-                    ],
-                    pattern: {
-                        size: 3,
-                        spacing: 10,
-                        margin: 10,
-                        variation: 0.5,
-                        offset: 0.5,
-                        singleColor: 0.7  // % chance that all dots in a cell will be the same color
-                    }
-                },
-                flower: {
-                    frequency: 0.05,
-                    colors: [
-                        'rgba(255, 105, 180, 0.3)',
-                        'rgba(221, 160, 221, 0.3)',
-                        'rgba(147, 112, 219, 0.3)'
-                    ],
-                    pattern: {
-                        size: 12,
-                        spacing: 16,
-                        rotation: Math.PI / 6,  // Base rotation of pattern
-                        margin: 10,
-                        variation: 0.2
-                    }
-                },
-                grass: {
-                    frequency: 0.12,
-                    colors: ['rgba(28, 48, 32, 0.25)'],
-                    hatch: {
-                        spacing: 8,
-                        length: 6,
-                        angle: Math.PI / 4,
-                        variation: 0.4,   // Slight randomness in angle
-                        margin: 4
-                    },
-                    spreadFactor: 0.6  // Add this for grass spreading
-                },
-                villagers: {
-                    count: 100, // Number of villagers to place
-                    color: '#424242',
-                    size: 30
-                }
-            }
-        }
-    },
-    collision: {
-        enabled: true,
-        vegetation: {
-            tree: {
-                enabled: true,
-                sizeMultiplier: 1.0
-            }
-        }
-    }
-};
-
-CONFIG.sword.colors = CONFIG.effects.colors;
-CONFIG.bubble.colors = CONFIG.effects.colors; 
\ No newline at end of file
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) => {