diff options
Diffstat (limited to 'html/plains')
-rw-r--r-- | html/plains/config.js | 163 | ||||
-rw-r--r-- | html/plains/game.js | 186 |
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) => { |