XTaylorSpenceX

Fractal Village

Sep 14th, 2025 (edited)
86
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
HTML 59.43 KB | None | 0 0
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4.     <meta charset="UTF-8">
  5.     <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6.     <title>Fractal Village - Medieval Planning Game</title>
  7.     <style>
  8.         * {
  9.             margin: 0;
  10.             padding: 0;
  11.             box-sizing: border-box;
  12.         }
  13.  
  14.         body {
  15.             font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
  16.             background: linear-gradient(135deg, #1e3c72, #2a5298);
  17.             color: #fff;
  18.             overflow: hidden;
  19.             position: relative;
  20.         }
  21.  
  22.         #gameContainer {
  23.             display: flex;
  24.             height: 100vh;
  25.             position: relative;
  26.         }
  27.  
  28.         #sidebar {
  29.             width: 280px;
  30.             background: linear-gradient(180deg, rgba(0,0,0,0.8), rgba(0,0,0,0.9));
  31.             padding: 20px;
  32.             overflow-y: auto;
  33.             border-right: 3px solid #ffd700;
  34.             box-shadow: 5px 0 20px rgba(0,0,0,0.5);
  35.             z-index: 10;
  36.         }
  37.  
  38.         h1 {
  39.             font-size: 24px;
  40.             color: #ffd700;
  41.             text-shadow: 2px 2px 4px rgba(0,0,0,0.8);
  42.             margin-bottom: 20px;
  43.             text-align: center;
  44.         }
  45.  
  46.         #resources {
  47.             background: rgba(255,255,255,0.1);
  48.             padding: 15px;
  49.             border-radius: 8px;
  50.             margin-bottom: 20px;
  51.             border: 2px solid #ffd700;
  52.         }
  53.  
  54.         .resource {
  55.             display: flex;
  56.             justify-content: space-between;
  57.             margin: 8px 0;
  58.             padding: 5px;
  59.             background: rgba(255,255,255,0.05);
  60.             border-radius: 4px;
  61.         }
  62.  
  63.         .resource-icon {
  64.             margin-right: 8px;
  65.         }
  66.  
  67.         .trade-buttons {
  68.             display: flex;
  69.             gap: 10px;
  70.             margin-top: 10px;
  71.         }
  72.  
  73.         .trade-btn {
  74.             flex: 1;
  75.             padding: 8px;
  76.             background: linear-gradient(135deg, #2ecc71, #27ae60);
  77.             color: white;
  78.             border: 2px solid #229954;
  79.             border-radius: 4px;
  80.             cursor: pointer;
  81.             font-size: 12px;
  82.             font-weight: bold;
  83.             transition: all 0.3s;
  84.         }
  85.  
  86.         .trade-btn:hover:not(:disabled) {
  87.             background: linear-gradient(135deg, #3edc81, #37be70);
  88.             transform: translateY(-1px);
  89.         }
  90.  
  91.         .trade-btn:disabled {
  92.             opacity: 0.5;
  93.             cursor: not-allowed;
  94.         }
  95.  
  96.         #buildMenu {
  97.             background: rgba(255,255,255,0.1);
  98.             padding: 15px;
  99.             border-radius: 8px;
  100.             margin-bottom: 20px;
  101.             border: 2px solid #ffd700;
  102.         }
  103.  
  104.         .building-btn {
  105.             width: 100%;
  106.             padding: 12px;
  107.             margin: 8px 0;
  108.             background: linear-gradient(135deg, #4a69bd, #3c5aa6);
  109.             color: white;
  110.             border: 2px solid #2c3e50;
  111.             border-radius: 6px;
  112.             cursor: pointer;
  113.             font-size: 14px;
  114.             font-weight: bold;
  115.             transition: all 0.3s;
  116.             position: relative;
  117.             overflow: hidden;
  118.         }
  119.  
  120.         .building-btn:hover:not(:disabled) {
  121.             background: linear-gradient(135deg, #5a79cd, #4c6ab6);
  122.             transform: translateY(-2px);
  123.             box-shadow: 0 5px 15px rgba(0,0,0,0.3);
  124.         }
  125.  
  126.         .building-btn.selected {
  127.             background: linear-gradient(135deg, #27ae60, #229954);
  128.             border-color: #ffd700;
  129.             box-shadow: 0 0 15px rgba(255,215,0,0.5);
  130.         }
  131.  
  132.         .building-btn:disabled {
  133.             opacity: 0.5;
  134.             cursor: not-allowed;
  135.         }
  136.  
  137.         .cost {
  138.             font-size: 11px;
  139.             color: #ffd700;
  140.             margin-top: 4px;
  141.         }
  142.  
  143.         #gameCanvas {
  144.             flex: 1;
  145.             position: relative;
  146.             background: #2c3e50;
  147.         }
  148.  
  149.         canvas {
  150.             display: block;
  151.             image-rendering: pixelated;
  152.             image-rendering: crisp-edges;
  153.             cursor: crosshair;
  154.         }
  155.  
  156.         #info {
  157.             position: absolute;
  158.             bottom: 20px;
  159.             left: 300px;
  160.             right: 20px;
  161.             background: linear-gradient(180deg, rgba(0,0,0,0.8), rgba(0,0,0,0.9));
  162.             padding: 15px;
  163.             border-radius: 8px;
  164.             border: 2px solid #ffd700;
  165.             max-width: 600px;
  166.             box-shadow: 0 -5px 20px rgba(0,0,0,0.5);
  167.             z-index: 10;
  168.         }
  169.  
  170.         #selectionInfo {
  171.             position: absolute;
  172.             top: 80px;
  173.             right: 20px;
  174.             background: rgba(0,0,0,0.9);
  175.             padding: 15px;
  176.             border-radius: 8px;
  177.             border: 2px solid #ffd700;
  178.             width: 250px;
  179.             display: none;
  180.             z-index: 10;
  181.         }
  182.  
  183.         #selectionInfo h4 {
  184.             color: #ffd700;
  185.             margin-bottom: 10px;
  186.         }
  187.  
  188.         #selectionInfo .stat {
  189.             margin: 5px 0;
  190.             font-size: 13px;
  191.         }
  192.  
  193.         #fractalLevel {
  194.             background: rgba(255,255,255,0.1);
  195.             padding: 15px;
  196.             border-radius: 8px;
  197.             border: 2px solid #ffd700;
  198.         }
  199.  
  200.         .level-indicator {
  201.             display: flex;
  202.             align-items: center;
  203.             margin: 10px 0;
  204.         }
  205.  
  206.         .level-dot {
  207.             width: 20px;
  208.             height: 20px;
  209.             border-radius: 50%;
  210.             background: #34495e;
  211.             margin: 0 5px;
  212.             transition: all 0.3s;
  213.         }
  214.  
  215.         .level-dot.active {
  216.             background: #ffd700;
  217.             box-shadow: 0 0 10px #ffd700;
  218.         }
  219.  
  220.         button {
  221.             padding: 8px 16px;
  222.             background: linear-gradient(135deg, #e67e22, #d35400);
  223.             color: white;
  224.             border: 2px solid #a04000;
  225.             border-radius: 6px;
  226.             cursor: pointer;
  227.             font-weight: bold;
  228.             transition: all 0.3s;
  229.             margin: 5px;
  230.         }
  231.  
  232.         button:hover:not(:disabled) {
  233.             background: linear-gradient(135deg, #f39c12, #e67e22);
  234.             transform: translateY(-2px);
  235.             box-shadow: 0 5px 15px rgba(0,0,0,0.3);
  236.         }
  237.  
  238.         #tooltip {
  239.             position: absolute;
  240.             background: rgba(0,0,0,0.95);
  241.             padding: 10px;
  242.             border-radius: 6px;
  243.             border: 2px solid #ffd700;
  244.             pointer-events: none;
  245.             display: none;
  246.             z-index: 1000;
  247.             font-size: 12px;
  248.             max-width: 250px;
  249.         }
  250.  
  251.         #controls {
  252.             position: absolute;
  253.             top: 20px;
  254.             right: 20px;
  255.             background: rgba(0,0,0,0.8);
  256.             padding: 15px;
  257.             border-radius: 8px;
  258.             border: 2px solid #ffd700;
  259.             z-index: 10;
  260.         }
  261.  
  262.         #statsInfo {
  263.             background: rgba(255,255,255,0.1);
  264.             padding: 10px;
  265.             border-radius: 8px;
  266.             border: 1px solid #ffd700;
  267.             margin-top: 10px;
  268.             font-size: 12px;
  269.         }
  270.  
  271.         .happiness-bar {
  272.             width: 100%;
  273.             height: 20px;
  274.             background: #333;
  275.             border-radius: 10px;
  276.             overflow: hidden;
  277.             margin: 5px 0;
  278.         }
  279.  
  280.         .happiness-fill {
  281.             height: 100%;
  282.             background: linear-gradient(90deg, #e74c3c, #f39c12, #2ecc71);
  283.             transition: width 0.3s;
  284.         }
  285.  
  286.         .selected-building {
  287.             animation: pulse 1s infinite;
  288.         }
  289.  
  290.         @keyframes pulse {
  291.             0%, 100% { opacity: 1; }
  292.             50% { opacity: 0.7; }
  293.         }
  294.     </style>
  295. </head>
  296. <body>
  297.     <div id="gameContainer">
  298.         <div id="sidebar">
  299.             <h1>🏰 Fractal Village 🏰</h1>
  300.            
  301.             <div id="resources">
  302.                 <h3>Resources</h3>
  303.                 <div class="resource">
  304.                     <span><span class="resource-icon">πŸͺ™</span>Gold</span>
  305.                     <span id="gold">100</span>
  306.                 </div>
  307.                 <div class="resource">
  308.                     <span><span class="resource-icon">πŸͺ΅</span>Wood</span>
  309.                     <span id="wood">50</span>
  310.                 </div>
  311.                 <div class="resource">
  312.                     <span><span class="resource-icon">⛏️</span>Stone</span>
  313.                     <span id="stone">30</span>
  314.                 </div>
  315.                 <div class="resource">
  316.                     <span><span class="resource-icon">🍞</span>Food</span>
  317.                     <span id="food">50</span>
  318.                 </div>
  319.                 <div class="resource">
  320.                     <span><span class="resource-icon">πŸ‘₯</span>Population</span>
  321.                     <span id="population">4</span>
  322.                 </div>
  323.                 <div class="trade-buttons">
  324.                     <button class="trade-btn" id="sellWood">
  325.                         Sell Wood<br>
  326.                         <small>100πŸͺ΅ β†’ 30πŸͺ™</small>
  327.                     </button>
  328.                     <button class="trade-btn" id="sellStone">
  329.                         Sell Stone<br>
  330.                         <small>100⛏️ β†’ 50πŸͺ™</small>
  331.                     </button>
  332.                 </div>
  333.             </div>
  334.  
  335.             <div id="buildMenu">
  336.                 <h3>Build Menu</h3>
  337.                 <button class="building-btn" data-type="house">
  338.                     🏠 House
  339.                     <div class="cost">πŸ’° 50 πŸͺ΅ 30</div>
  340.                 </button>
  341.                 <button class="building-btn" data-type="farm">
  342.                     🌾 Farm
  343.                     <div class="cost">πŸ’° 80 πŸͺ΅ 40</div>
  344.                 </button>
  345.                 <button class="building-btn" data-type="lumber">
  346.                     πŸͺ“ Lumber Mill
  347.                     <div class="cost">πŸ’° 100 ⛏️ 20</div>
  348.                 </button>
  349.                 <button class="building-btn" data-type="quarry">
  350.                     ⛏️ Quarry
  351.                     <div class="cost">πŸ’° 120 πŸͺ΅ 50</div>
  352.                 </button>
  353.                 <button class="building-btn" data-type="market">
  354.                     πŸͺ Market
  355.                     <div class="cost">πŸ’° 200 πŸͺ΅ 80 ⛏️ 60</div>
  356.                 </button>
  357.                 <button class="building-btn" data-type="castle">
  358.                     🏰 Castle (District)
  359.                     <div class="cost">πŸ’° 500 πŸͺ΅ 200 ⛏️ 150</div>
  360.                 </button>
  361.                 <button class="building-btn" data-type="delete">
  362.                     πŸ—‘οΈ Delete Mode
  363.                     <div class="cost">Free</div>
  364.                 </button>
  365.             </div>
  366.  
  367.             <div id="fractalLevel">
  368.                 <h3>Fractal Depth</h3>
  369.                 <div class="level-indicator">
  370.                     <span>Level:</span>
  371.                     <div class="level-dot active"></div>
  372.                     <div class="level-dot"></div>
  373.                     <div class="level-dot"></div>
  374.                 </div>
  375.                 <button id="zoomIn">πŸ” Enter District</button>
  376.                 <button id="zoomOut">πŸ”™ Exit District</button>
  377.             </div>
  378.  
  379.             <div id="statsInfo">
  380.                 <h4>Village Stats</h4>
  381.                 <div>😊 Happiness: <span id="happinessLevel">75%</span></div>
  382.                 <div class="happiness-bar">
  383.                     <div class="happiness-fill" id="happinessBar" style="width: 75%"></div>
  384.                 </div>
  385.                 <div>🚢 Working: <span id="workingCount">0</span></div>
  386.                 <div>😴 Resting: <span id="restingCount">0</span></div>
  387.                 <div>🍞 Food Status: <span id="foodStatus">Adequate</span></div>
  388.             </div>
  389.         </div>
  390.  
  391.         <div id="gameCanvas">
  392.             <canvas id="canvas"></canvas>
  393.             <div id="controls">
  394.                 <button id="pauseBtn">⏸️ Pause</button>
  395.                 <button id="speedBtn">⏩ Speed: 1x</button>
  396.             </div>
  397.             <div id="selectionInfo">
  398.                 <h4 id="selectionTitle">Building Info</h4>
  399.                 <div id="selectionDetails"></div>
  400.             </div>
  401.         </div>
  402.  
  403.         <div id="info">
  404.             <p>Left-click buildings to see details. Right-click workers to check happiness. Trade resources to grow your village!</p>
  405.         </div>
  406.  
  407.         <div id="tooltip"></div>
  408.     </div>
  409.  
  410.     <script>
  411.         const canvas = document.getElementById('canvas');
  412.         const ctx = canvas.getContext('2d');
  413.        
  414.         // Constants
  415.         const TILE_SIZE = 32;
  416.         const MAP_WIDTH = 25;
  417.         const MAP_HEIGHT = 18;
  418.         const FOOD_PER_PERSON = 0.15; // Food consumed per person per second
  419.        
  420.         // Tile types
  421.         const TILES = {
  422.             GRASS: 0,
  423.             GRASS_ALT: 1,
  424.             TREE: 2,
  425.             ROCK: 3,
  426.             WATER: 4,
  427.             DIRT: 5,
  428.             FLOWERS: 6
  429.         };
  430.        
  431.         // Game state
  432.         const game = {
  433.             gold: 100,
  434.             wood: 50,
  435.             stone: 30,
  436.             food: 50,
  437.             population: 4,
  438.             happiness: 75,
  439.             foodSurplus: true,
  440.             paused: false,
  441.             speed: 1,
  442.             selectedBuilding: null,
  443.             selectedObject: null,
  444.             fractalLevel: 0,
  445.             maxFractalLevel: 2,
  446.             currentDistrict: null,
  447.             districts: new Map(),
  448.             lastTime: 0,
  449.             accumulator: 0,
  450.             workers: [],
  451.             hoverPos: null,
  452.             markets: []
  453.         };
  454.  
  455.         // Building types
  456.         const buildings = {
  457.             house: {
  458.                 tile: '🏠',
  459.                 color: '#8b4513',
  460.                 cost: { gold: 50, wood: 30 },
  461.                 provides: { population: 4 },
  462.                 consumes: { food: FOOD_PER_PERSON * 4 },
  463.                 size: 1
  464.             },
  465.             farm: {
  466.                 tile: '🌾',
  467.                 color: '#90ee90',
  468.                 cost: { gold: 80, wood: 40 },
  469.                 provides: { food: 2 },
  470.                 size: 2,
  471.                 needsWorker: true
  472.             },
  473.             lumber: {
  474.                 tile: 'πŸͺ“',
  475.                 color: '#654321',
  476.                 cost: { gold: 100, stone: 20 },
  477.                 provides: { wood: 3 },
  478.                 size: 2,
  479.                 needsWorker: true
  480.             },
  481.             quarry: {
  482.                 tile: '⛏️',
  483.                 color: '#696969',
  484.                 cost: { gold: 120, wood: 50 },
  485.                 provides: { stone: 2 },
  486.                 size: 2,
  487.                 needsWorker: true
  488.             },
  489.             market: {
  490.                 tile: 'πŸͺ',
  491.                 color: '#ffd700',
  492.                 cost: { gold: 200, wood: 80, stone: 60 },
  493.                 provides: { gold: 5 },
  494.                 autoTrade: { wood: 2, stone: 1 }, // Trades 2 wood or 1 stone for 1 gold
  495.                 size: 2,
  496.                 needsWorker: true
  497.             },
  498.             castle: {
  499.                 tile: '🏰',
  500.                 color: '#4a4a4a',
  501.                 cost: { gold: 500, wood: 200, stone: 150 },
  502.                 provides: {},
  503.                 size: 3,
  504.                 isDistrict: true
  505.             }
  506.         };
  507.  
  508.         // Worker class
  509.         class Worker {
  510.             constructor(home, workplace) {
  511.                 this.home = home;
  512.                 this.workplace = workplace;
  513.                 this.x = home.x * TILE_SIZE + TILE_SIZE/2;
  514.                 this.y = home.y * TILE_SIZE + TILE_SIZE/2;
  515.                 this.targetX = this.x;
  516.                 this.targetY = this.y;
  517.                 this.state = 'working';
  518.                 this.stateTimer = Math.random() * 10 + 5;
  519.                 this.speed = 40;
  520.                 this.baseSpeed = 40;
  521.                 this.tile = ['πŸ‘¨β€πŸŒΎ', 'πŸ‘©β€πŸŒΎ', 'πŸ§‘β€πŸŒΎ'][Math.floor(Math.random() * 3)];
  522.                 this.happiness = 75;
  523.                 this.productivity = 1.0;
  524.             }
  525.  
  526.             update(deltaTime) {
  527.                 if (game.paused) return;
  528.                
  529.                 // Update happiness based on food status
  530.                 if (game.foodSurplus) {
  531.                     this.happiness = Math.min(100, this.happiness + deltaTime * 2);
  532.                 } else if (game.food <= 0) {
  533.                    this.happiness = Math.max(0, this.happiness - deltaTime * 5);
  534.                } else {
  535.                    this.happiness = Math.max(50, Math.min(75, this.happiness));
  536.                }
  537.                
  538.                // Happiness affects productivity and speed
  539.                this.productivity = 0.5 + (this.happiness / 200);
  540.                this.speed = this.baseSpeed * (0.7 + this.happiness / 300);
  541.                
  542.                // Low happiness makes workers rest more
  543.                const restModifier = this.happiness < 30 ? 2 : 1;
  544.                
  545.                this.stateTimer -= deltaTime * game.speed;
  546.                if (this.stateTimer <= 0) {
  547.                    if (this.state === 'working') {
  548.                        this.state = 'resting';
  549.                        this.stateTimer = (Math.random() * 5 + 3) * restModifier;
  550.                        this.setTarget(this.home);
  551.                    } else if (this.state === 'resting') {
  552.                        this.state = 'working';
  553.                        this.stateTimer = Math.random() * 10 + 8;
  554.                        this.setTarget(this.workplace);
  555.                    }
  556.                }
  557.  
  558.                const dx = this.targetX - this.x;
  559.                const dy = this.targetY - this.y;
  560.                const dist = Math.sqrt(dx * dx + dy * dy);
  561.                
  562.                if (dist > 2) {
  563.                     const moveX = (dx / dist) * this.speed * deltaTime * game.speed;
  564.                     const moveY = (dy / dist) * this.speed * deltaTime * game.speed;
  565.                     this.x += moveX;
  566.                     this.y += moveY;
  567.                 }
  568.             }
  569.  
  570.             setTarget(building) {
  571.                 if (!building) return;
  572.                 const size = buildings[building.type] ? buildings[building.type].size : 1;
  573.                 this.targetX = building.x * TILE_SIZE + (size * TILE_SIZE) / 2;
  574.                 this.targetY = building.y * TILE_SIZE + (size * TILE_SIZE) / 2;
  575.             }
  576.  
  577.             draw() {
  578.                 // Shadow
  579.                 ctx.fillStyle = 'rgba(0,0,0,0.3)';
  580.                 ctx.beginPath();
  581.                 ctx.ellipse(this.x, this.y + 8, 6, 3, 0, 0, Math.PI * 2);
  582.                 ctx.fill();
  583.                
  584.                 // Worker tile with happiness tint
  585.                 ctx.save();
  586.                 if (this.happiness < 30) {
  587.                    ctx.filter = 'hue-rotate(-30deg) saturate(0.5)';
  588.                } else if (this.happiness > 80) {
  589.                     ctx.filter = 'hue-rotate(10deg) saturate(1.2)';
  590.                 }
  591.                
  592.                 ctx.font = '16px serif';
  593.                 ctx.textAlign = 'center';
  594.                 ctx.textBaseline = 'middle';
  595.                 ctx.fillText(this.tile, this.x, this.y);
  596.                 ctx.restore();
  597.                
  598.                 // State indicator
  599.                 if (this.state === 'resting') {
  600.                     ctx.font = '12px serif';
  601.                     ctx.fillText('πŸ’€', this.x + 8, this.y - 8);
  602.                 } else if (this.happiness < 30) {
  603.                    ctx.font = '12px serif';
  604.                    ctx.fillText('😞', this.x + 8, this.y - 8);
  605.                } else if (this.happiness > 80) {
  606.                     ctx.font = '12px serif';
  607.                     ctx.fillText('😊', this.x + 8, this.y - 8);
  608.                 }
  609.             }
  610.         }
  611.  
  612.         // District class
  613.         class District {
  614.             constructor(level) {
  615.                 this.level = level;
  616.                 this.tiles = Array(MAP_HEIGHT).fill().map(() => Array(MAP_WIDTH).fill(TILES.GRASS));
  617.                 this.buildings = [];
  618.                 this.grid = Array(MAP_HEIGHT).fill().map(() => Array(MAP_WIDTH).fill(null));
  619.                
  620.                 this.generateTerrain();
  621.                 if (level === 0) {
  622.                     this.addStartingBuildings();
  623.                 }
  624.             }
  625.  
  626.             generateTerrain() {
  627.                 for (let y = 0; y < MAP_HEIGHT; y++) {
  628.                    for (let x = 0; x < MAP_WIDTH; x++) {
  629.                        this.tiles[y][x] = Math.random() < 0.7 ? TILES.GRASS : TILES.GRASS_ALT;
  630.                        
  631.                        if (Math.random() < 0.05) {
  632.                            this.tiles[y][x] = TILES.FLOWERS;
  633.                        }
  634.                        
  635.                        if (x > 8 && Math.random() < 0.08) {
  636.                            this.grid[y][x] = { type: 'tree', tile: TILES.TREE };
  637.                         } else if (x > 8 && Math.random() < 0.03) {
  638.                            this.grid[y][x] = { type: 'rock', tile: TILES.ROCK };
  639.                         }
  640.                     }
  641.                 }
  642.                
  643.                 if (Math.random() < 0.3) {
  644.                    const riverX = Math.floor(MAP_WIDTH * 0.7 + Math.random() * 5);
  645.                    for (let y = 0; y < MAP_HEIGHT; y++) {
  646.                        if (Math.random() < 0.6) {
  647.                            this.tiles[y][riverX] = TILES.WATER;
  648.                            if (riverX > 0) this.tiles[y][riverX-1] = TILES.DIRT;
  649.                             if (riverX < MAP_WIDTH-1) this.tiles[y][riverX+1] = TILES.DIRT;
  650.                        }
  651.                    }
  652.                }
  653.            }
  654.  
  655.            addStartingBuildings() {
  656.                for (let y = 2; y < 8; y++) {
  657.                    for (let x = 2; x < 8; x++) {
  658.                        this.grid[y][x] = null;
  659.                        this.tiles[y][x] = TILES.DIRT;
  660.                    }
  661.                }
  662.                
  663.                this.addBuilding(2, 3, 'house', true);
  664.                this.addBuilding(4, 3, 'farm', true);
  665.                this.addBuilding(2, 5, 'lumber', true);
  666.            }
  667.  
  668.            addBuilding(x, y, type, skipCost) {
  669.                const building = buildings[type];
  670.                if (!building) return false;
  671.  
  672.                for (let dy = 0; dy < building.size; dy++) {
  673.                    for (let dx = 0; dx < building.size; dx++) {
  674.                        if (y + dy >= MAP_HEIGHT || x + dx >= MAP_WIDTH) return false;
  675.                         if (this.grid[y + dy][x + dx] !== null) return false;
  676.                         if (this.tiles[y + dy][x + dx] === TILES.WATER) return false;
  677.                     }
  678.                 }
  679.  
  680.                 if (!skipCost) {
  681.                     for (let resource in building.cost) {
  682.                         if (game[resource] < building.cost[resource]) return false;
  683.                    }
  684.                    for (let resource in building.cost) {
  685.                        game[resource] -= building.cost[resource];
  686.                    }
  687.                }
  688.  
  689.                const buildingData = {
  690.                    type: type,
  691.                    x: x,
  692.                    y: y,
  693.                    size: building.size,
  694.                    id: Date.now() + Math.random(),
  695.                    production: 0,
  696.                    worker: null
  697.                };
  698.  
  699.                for (let dy = 0; dy < building.size; dy++) {
  700.                    for (let dx = 0; dx < building.size; dx++) {
  701.                        this.grid[y + dy][x + dx] = buildingData;
  702.                        this.tiles[y + dy][x + dx] = TILES.DIRT;
  703.                    }
  704.                }
  705.  
  706.                this.buildings.push(buildingData);
  707.  
  708.                if (building.provides && building.provides.population) {
  709.                    game.population += building.provides.population;
  710.                }
  711.  
  712.                if (type === 'market') {
  713.                    game.markets.push(buildingData);
  714.                }
  715.  
  716.                if (building.needsWorker && this.level === game.fractalLevel) {
  717.                    const house = this.buildings.find(b => b.type === 'house');
  718.                     if (house) {
  719.                         const worker = new Worker(house, buildingData);
  720.                         game.workers.push(worker);
  721.                         buildingData.worker = worker;
  722.                     }
  723.                 }
  724.  
  725.                 if (building.isDistrict && this.level < game.maxFractalLevel) {
  726.                    const key = this.level + '-' + x + '-' + y;
  727.                     game.districts.set(key, new District(this.level + 1));
  728.                 }
  729.  
  730.                 return true;
  731.             }
  732.  
  733.             removeBuilding(x, y) {
  734.                 const cell = this.grid[y] && this.grid[y][x];
  735.                 if (!cell) return false;
  736.                
  737.                 if (cell.type === 'tree' || cell.type === 'rock') {
  738.                     this.grid[y][x] = null;
  739.                     this.tiles[y][x] = TILES.GRASS;
  740.                     return true;
  741.                 }
  742.                
  743.                 if (!cell.type) return false;
  744.  
  745.                 const building = buildings[cell.type];
  746.                 const bx = cell.x;
  747.                 const by = cell.y;
  748.  
  749.                 game.workers = game.workers.filter(w => w.workplace.id !== cell.id);
  750.                
  751.                 if (cell.type === 'market') {
  752.                     game.markets = game.markets.filter(m => m.id !== cell.id);
  753.                 }
  754.  
  755.                 for (let dy = 0; dy < building.size; dy++) {
  756.                    for (let dx = 0; dx < building.size; dx++) {
  757.                        if (this.grid[by + dy] && this.grid[by + dy][bx + dx]) {
  758.                            this.grid[by + dy][bx + dx] = null;
  759.                            this.tiles[by + dy][bx + dx] = TILES.GRASS;
  760.                        }
  761.                    }
  762.                }
  763.  
  764.                this.buildings = this.buildings.filter(b => b.id !== cell.id);
  765.  
  766.                 if (building.provides && building.provides.population) {
  767.                    game.population = Math.max(0, game.population - building.provides.population);
  768.                 }
  769.  
  770.                 for (let resource in building.cost) {
  771.                     game[resource] += Math.floor(building.cost[resource] * 0.5);
  772.                 }
  773.  
  774.                 return true;
  775.             }
  776.         }
  777.  
  778.         // Draw tile
  779.         function drawTile(tileType, x, y) {
  780.             const px = x * TILE_SIZE;
  781.             const py = y * TILE_SIZE;
  782.            
  783.             switch(tileType) {
  784.                 case TILES.GRASS:
  785.                     ctx.fillStyle = '#4a7c59';
  786.                     ctx.fillRect(px, py, TILE_SIZE, TILE_SIZE);
  787.                     break;
  788.                 case TILES.GRASS_ALT:
  789.                     ctx.fillStyle = '#5a8c69';
  790.                     ctx.fillRect(px, py, TILE_SIZE, TILE_SIZE);
  791.                     break;
  792.                 case TILES.TREE:
  793.                     ctx.fillStyle = '#4a7c59';
  794.                     ctx.fillRect(px, py, TILE_SIZE, TILE_SIZE);
  795.                     ctx.font = '24px serif';
  796.                     ctx.textAlign = 'center';
  797.                     ctx.textBaseline = 'middle';
  798.                     ctx.fillText('🌲', px + TILE_SIZE/2, py + TILE_SIZE/2);
  799.                     break;
  800.                 case TILES.ROCK:
  801.                     ctx.fillStyle = '#5a8c69';
  802.                     ctx.fillRect(px, py, TILE_SIZE, TILE_SIZE);
  803.                     ctx.font = '20px serif';
  804.                     ctx.textAlign = 'center';
  805.                     ctx.textBaseline = 'middle';
  806.                     ctx.fillText('πŸͺ¨', px + TILE_SIZE/2, py + TILE_SIZE/2);
  807.                     break;
  808.                 case TILES.WATER:
  809.                     ctx.fillStyle = '#4682b4';
  810.                     ctx.fillRect(px, py, TILE_SIZE, TILE_SIZE);
  811.                     break;
  812.                 case TILES.DIRT:
  813.                     ctx.fillStyle = '#8b7355';
  814.                     ctx.fillRect(px, py, TILE_SIZE, TILE_SIZE);
  815.                     break;
  816.                 case TILES.FLOWERS:
  817.                     ctx.fillStyle = '#4a7c59';
  818.                     ctx.fillRect(px, py, TILE_SIZE, TILE_SIZE);
  819.                     ctx.font = '16px serif';
  820.                     ctx.textAlign = 'center';
  821.                     ctx.textBaseline = 'middle';
  822.                     ctx.fillText('🌼', px + TILE_SIZE/2, py + TILE_SIZE/2);
  823.                     break;
  824.             }
  825.            
  826.             ctx.strokeStyle = 'rgba(0,0,0,0.05)';
  827.             ctx.strokeRect(px, py, TILE_SIZE, TILE_SIZE);
  828.         }
  829.  
  830.         // Initialize
  831.         function init() {
  832.             canvas.width = MAP_WIDTH * TILE_SIZE;
  833.             canvas.height = MAP_HEIGHT * TILE_SIZE;
  834.            
  835.             game.districts.set('main', new District(0));
  836.             game.currentDistrict = game.districts.get('main');
  837.            
  838.             game.currentDistrict.buildings.forEach(building => {
  839.                 const type = buildings[building.type];
  840.                 if (type && type.needsWorker) {
  841.                    const house = game.currentDistrict.buildings.find(b => b.type === 'house');
  842.                     if (house) {
  843.                         const worker = new Worker(house, building);
  844.                         game.workers.push(worker);
  845.                         building.worker = worker;
  846.                     }
  847.                 }
  848.             });
  849.            
  850.             updateUI();
  851.         }
  852.  
  853.         // Update resources
  854.         function updateResources(deltaTime) {
  855.             if (game.paused) return;
  856.            
  857.             const multiplier = deltaTime * game.speed;
  858.            
  859.             // Production from buildings
  860.             game.currentDistrict.buildings.forEach(building => {
  861.                 const type = buildings[building.type];
  862.                 if (type && type.provides && !type.provides.population) {
  863.                    let productivity = 1.0;
  864.                    
  865.                     if (type.needsWorker) {
  866.                         const worker = game.workers.find(w => w.workplace.id === building.id);
  867.                         if (worker && worker.state === 'working') {
  868.                            productivity = worker.productivity;
  869.                         } else {
  870.                             productivity = 0;
  871.                         }
  872.                     }
  873.                    
  874.                     for (let resource in type.provides) {
  875.                         const amount = type.provides[resource] * multiplier * productivity;
  876.                         game[resource] += amount;
  877.                         building.production += amount;
  878.                     }
  879.                 }
  880.                
  881.                 // Market auto-trading
  882.                 if (building.type === 'market' && building.worker) {
  883.                    const worker = building.worker;
  884.                     if (worker.state === 'working') {
  885.                         // Trade wood for gold
  886.                         if (game.wood >= 2) {
  887.                             const tradeAmount = Math.min(2 * multiplier * worker.productivity, game.wood / 2);
  888.                             game.wood -= tradeAmount * 2;
  889.                             game.gold += tradeAmount;
  890.                         }
  891.                         // Trade stone for gold
  892.                         if (game.stone >= 1) {
  893.                             const tradeAmount = Math.min(multiplier * worker.productivity, game.stone);
  894.                             game.stone -= tradeAmount;
  895.                             game.gold += tradeAmount;
  896.                         }
  897.                     }
  898.                 }
  899.             });
  900.            
  901.             // Food consumption
  902.             const foodConsumption = game.population * FOOD_PER_PERSON * multiplier;
  903.             game.food -= foodConsumption;
  904.            
  905.             // Check food status
  906.             const foodProduction = game.currentDistrict.buildings
  907.                 .filter(b => b.type === 'farm')
  908.                 .reduce((sum, b) => {
  909.                     const worker = game.workers.find(w => w.workplace.id === b.id);
  910.                     return sum + (worker && worker.state === 'working' ? 2 * worker.productivity : 0);
  911.                 }, 0);
  912.            
  913.             game.foodSurplus = foodProduction > game.population * FOOD_PER_PERSON;
  914.            
  915.             if (game.food < 0) {
  916.                game.food = 0;
  917.                // Population decline from starvation
  918.                if (Math.random() < 0.01 * multiplier) {
  919.                    game.population = Math.max(1, game.population - 1);
  920.                }
  921.            }
  922.            
  923.            // Update happiness
  924.            if (game.foodSurplus) {
  925.                game.happiness = Math.min(100, game.happiness + deltaTime * 2);
  926.            } else if (game.food <= 0) {
  927.                game.happiness = Math.max(0, game.happiness - deltaTime * 5);
  928.            } else {
  929.                game.happiness = Math.max(50, Math.min(75, game.happiness));
  930.            }
  931.            
  932.            // Update UI stats
  933.            const working = game.workers.filter(w => w.state === 'working').length;
  934.             const resting = game.workers.filter(w => w.state === 'resting').length;
  935.             document.getElementById('workingCount').textContent = working;
  936.             document.getElementById('restingCount').textContent = resting;
  937.             document.getElementById('happinessLevel').textContent = Math.floor(game.happiness) + '%';
  938.             document.getElementById('happinessBar').style.width = game.happiness + '%';
  939.            
  940.             let foodStatus = 'Adequate';
  941.             if (game.foodSurplus) foodStatus = 'Surplus 😊';
  942.             else if (game.food <= 0) foodStatus = 'Starving! 😰';
  943.            else if (game.food < 20) foodStatus = 'Low ⚠️';
  944.            document.getElementById('foodStatus').textContent = foodStatus;
  945.        }
  946.  
  947.        // Render
  948.        function render() {
  949.            const district = game.currentDistrict;
  950.            
  951.            // Draw terrain tiles
  952.            for (let y = 0; y < MAP_HEIGHT; y++) {
  953.                for (let x = 0; x < MAP_WIDTH; x++) {
  954.                    drawTile(district.tiles[y][x], x, y);
  955.                }
  956.            }
  957.            
  958.            // Draw objects and buildings
  959.            const drawnBuildings = new Set();
  960.            for (let y = 0; y < MAP_HEIGHT; y++) {
  961.                for (let x = 0; x < MAP_WIDTH; x++) {
  962.                    const cell = district.grid[y][x];
  963.                    if (!cell) continue;
  964.                    
  965.                    if (cell.type === 'tree') {
  966.                        drawTile(TILES.TREE, x, y);
  967.                    } else if (cell.type === 'rock') {
  968.                        drawTile(TILES.ROCK, x, y);
  969.                    } else if (!drawnBuildings.has(cell.id)) {
  970.                        drawnBuildings.add(cell.id);
  971.                        const building = buildings[cell.type];
  972.                        
  973.                        // Highlight selected building
  974.                        if (game.selectedObject && game.selectedObject.id === cell.id) {
  975.                            ctx.strokeStyle = '#ffd700';
  976.                            ctx.lineWidth = 3;
  977.                            ctx.strokeRect(
  978.                                cell.x * TILE_SIZE - 2,
  979.                                cell.y * TILE_SIZE - 2,
  980.                                building.size * TILE_SIZE + 4,
  981.                                building.size * TILE_SIZE + 4
  982.                            );
  983.                        }
  984.                        
  985.                        // Building shadow
  986.                        ctx.fillStyle = 'rgba(0,0,0,0.2)';
  987.                        ctx.fillRect(
  988.                            cell.x * TILE_SIZE + 4,
  989.                            cell.y * TILE_SIZE + 4,
  990.                            building.size * TILE_SIZE - 2,
  991.                            building.size * TILE_SIZE - 2
  992.                        );
  993.                        
  994.                        // Building base
  995.                        ctx.fillStyle = building.color;
  996.                        ctx.fillRect(
  997.                            cell.x * TILE_SIZE + 1,
  998.                            cell.y * TILE_SIZE + 1,
  999.                            building.size * TILE_SIZE - 2,
  1000.                            building.size * TILE_SIZE - 2
  1001.                        );
  1002.                        
  1003.                        // Building tile
  1004.                        ctx.font = (building.size * 18) + 'px serif';
  1005.                        ctx.textAlign = 'center';
  1006.                        ctx.textBaseline = 'middle';
  1007.                        ctx.fillText(
  1008.                            building.tile,
  1009.                            cell.x * TILE_SIZE + (building.size * TILE_SIZE) / 2,
  1010.                            cell.y * TILE_SIZE + (building.size * TILE_SIZE) / 2
  1011.                        );
  1012.                    }
  1013.                }
  1014.            }
  1015.            
  1016.            // Draw workers
  1017.            game.workers.forEach(worker => worker.draw());
  1018.            
  1019.             // Draw hover preview
  1020.             if (game.selectedBuilding && game.selectedBuilding !== 'delete' && game.hoverPos) {
  1021.                const building = buildings[game.selectedBuilding];
  1022.                 const gx = Math.floor(game.hoverPos.x / TILE_SIZE);
  1023.                 const gy = Math.floor(game.hoverPos.y / TILE_SIZE);
  1024.                
  1025.                 if (gx >= 0 && gy >= 0 && gx < MAP_WIDTH && gy < MAP_HEIGHT) {
  1026.                    let canPlace = true;
  1027.                    
  1028.                     for (let dy = 0; dy < building.size; dy++) {
  1029.                        for (let dx = 0; dx < building.size; dx++) {
  1030.                            if (gy + dy >= MAP_HEIGHT || gx + dx >= MAP_WIDTH ||
  1031.                                 district.grid[gy + dy][gx + dx] !== null ||
  1032.                                 district.tiles[gy + dy][gx + dx] === TILES.WATER) {
  1033.                                 canPlace = false;
  1034.                             }
  1035.                         }
  1036.                     }
  1037.                    
  1038.                     for (let resource in building.cost) {
  1039.                         if (game[resource] < building.cost[resource]) canPlace = false;
  1040.                    }
  1041.                    
  1042.                    ctx.fillStyle = canPlace ? 'rgba(0, 255, 0, 0.3)' : 'rgba(255, 0, 0, 0.3)';
  1043.                    ctx.fillRect(gx * TILE_SIZE, gy * TILE_SIZE, building.size * TILE_SIZE, building.size * TILE_SIZE);
  1044.                }
  1045.            }
  1046.        }
  1047.  
  1048.        // Update UI
  1049.        function updateUI() {
  1050.            document.getElementById('gold').textContent = Math.floor(game.gold);
  1051.            document.getElementById('wood').textContent = Math.floor(game.wood);
  1052.            document.getElementById('stone').textContent = Math.floor(game.stone);
  1053.            document.getElementById('food').textContent = Math.floor(game.food);
  1054.            document.getElementById('population').textContent = game.population;
  1055.            
  1056.            document.querySelectorAll('.level-dot').forEach((dot, i) => {
  1057.                 dot.classList.toggle('active', i <= game.fractalLevel);
  1058.            });
  1059.            
  1060.            document.querySelectorAll('.building-btn').forEach(btn => {
  1061.                 const type = btn.dataset.type;
  1062.                 if (type === 'delete') return;
  1063.                
  1064.                 const building = buildings[type];
  1065.                 let canAfford = true;
  1066.                
  1067.                 for (let resource in building.cost) {
  1068.                     if (game[resource] < building.cost[resource]) canAfford = false;
  1069.                }
  1070.                
  1071.                btn.disabled = !canAfford;
  1072.            });
  1073.            
  1074.            // Update trade buttons
  1075.            document.getElementById('sellWood').disabled = game.wood < 100;
  1076.            document.getElementById('sellStone').disabled = game.stone < 100;
  1077.        }
  1078.  
  1079.        // Show building info
  1080.        function showBuildingInfo(building) {
  1081.            const info = document.getElementById('selectionInfo');
  1082.            const title = document.getElementById('selectionTitle');
  1083.            const details = document.getElementById('selectionDetails');
  1084.            
  1085.            const type = buildings[building.type];
  1086.            title.textContent = building.type.charAt(0).toUpperCase() + building.type.slice(1);
  1087.            
  1088.            let html = `<div class="stat">${type.tile} ${building.type}</div>`;
  1089.             html += `<div class="stat">Position: ${building.x}, ${building.y}</div>`;
  1090.            
  1091.             if (type.provides) {
  1092.                 for (let resource in type.provides) {
  1093.                     if (resource !== 'population') {
  1094.                         html += `<div class="stat">Produces: ${type.provides[resource]} ${resource}/s</div>`;
  1095.                     } else {
  1096.                         html += `<div class="stat">Houses: ${type.provides[resource]} people</div>`;
  1097.                     }
  1098.                 }
  1099.             }
  1100.            
  1101.             if (type.consumes) {
  1102.                 for (let resource in type.consumes) {
  1103.                     html += `<div class="stat">Consumes: ${type.consumes[resource].toFixed(2)} ${resource}/s</div>`;
  1104.                 }
  1105.             }
  1106.            
  1107.             if (building.worker) {
  1108.                 const worker = game.workers.find(w => w.workplace.id === building.id);
  1109.                 if (worker) {
  1110.                     html += `<div class="stat">Worker: ${worker.tile}</div>`;
  1111.                     html += `<div class="stat">Happiness: ${Math.floor(worker.happiness)}%</div>`;
  1112.                     html += `<div class="stat">Productivity: ${Math.floor(worker.productivity * 100)}%</div>`;
  1113.                     html += `<div class="stat">Status: ${worker.state}</div>`;
  1114.                 }
  1115.             } else if (type.needsWorker) {
  1116.                 html += `<div class="stat">Worker: None assigned</div>`;
  1117.             }
  1118.            
  1119.             if (building.type === 'market') {
  1120.                 html += `<div class="stat">Auto-trades resources for gold</div>`;
  1121.             }
  1122.            
  1123.             details.innerHTML = html;
  1124.             info.style.display = 'block';
  1125.         }
  1126.  
  1127.         // Show worker info
  1128.         function showWorkerInfo(worker) {
  1129.             const info = document.getElementById('selectionInfo');
  1130.             const title = document.getElementById('selectionTitle');
  1131.             const details = document.getElementById('selectionDetails');
  1132.            
  1133.             title.textContent = 'Worker Info';
  1134.            
  1135.             let html = `<div class="stat">Worker: ${worker.tile}</div>`;
  1136.             html += `<div class="stat">Happiness: ${Math.floor(worker.happiness)}%</div>`;
  1137.             html += `<div class="stat">Productivity: ${Math.floor(worker.productivity * 100)}%</div>`;
  1138.             html += `<div class="stat">Status: ${worker.state}</div>`;
  1139.            
  1140.             const workplace = game.currentDistrict.buildings.find(b => b.id === worker.workplace.id);
  1141.             if (workplace) {
  1142.                 html += `<div class="stat">Works at: ${workplace.type}</div>`;
  1143.             }
  1144.            
  1145.             if (worker.happiness < 30) {
  1146.                html += `<div class="stat" style="color: #e74c3c;">Very unhappy - needs food!</div>`;
  1147.             } else if (worker.happiness > 80) {
  1148.                 html += `<div class="stat" style="color: #2ecc71;">Very happy - food surplus!</div>`;
  1149.             }
  1150.            
  1151.             details.innerHTML = html;
  1152.             info.style.display = 'block';
  1153.         }
  1154.  
  1155.         // Event handlers
  1156.         document.querySelectorAll('.building-btn').forEach(btn => {
  1157.             btn.addEventListener('click', function() {
  1158.                 document.querySelectorAll('.building-btn').forEach(b =>
  1159.                     b.classList.remove('selected')
  1160.                 );
  1161.                 btn.classList.add('selected');
  1162.                 game.selectedBuilding = btn.dataset.type;
  1163.                 game.selectedObject = null;
  1164.                 document.getElementById('selectionInfo').style.display = 'none';
  1165.             });
  1166.         });
  1167.  
  1168.         // Trade buttons
  1169.         document.getElementById('sellWood').addEventListener('click', function() {
  1170.             if (game.wood >= 100) {
  1171.                 game.wood -= 100;
  1172.                 game.gold += 30;
  1173.                 updateUI();
  1174.                 createParticle(150, 250, 'πŸ’°');
  1175.             }
  1176.         });
  1177.  
  1178.         document.getElementById('sellStone').addEventListener('click', function() {
  1179.             if (game.stone >= 100) {
  1180.                 game.stone -= 100;
  1181.                 game.gold += 50;
  1182.                 updateUI();
  1183.                 createParticle(230, 250, 'πŸ’°');
  1184.             }
  1185.         });
  1186.  
  1187.         canvas.addEventListener('mousemove', function(e) {
  1188.             const rect = canvas.getBoundingClientRect();
  1189.             game.hoverPos = {
  1190.                 x: e.clientX - rect.left,
  1191.                 y: e.clientY - rect.top
  1192.             };
  1193.         });
  1194.  
  1195.         // Left click for selection
  1196.         canvas.addEventListener('click', function(e) {
  1197.             if (!game.hoverPos) return;
  1198.            
  1199.             const gx = Math.floor(game.hoverPos.x / TILE_SIZE);
  1200.             const gy = Math.floor(game.hoverPos.y / TILE_SIZE);
  1201.            
  1202.             if (gx < 0 || gy < 0 || gx >= MAP_WIDTH || gy >= MAP_HEIGHT) return;
  1203.            
  1204.             if (game.selectedBuilding === 'delete') {
  1205.                 if (game.currentDistrict.removeBuilding(gx, gy)) {
  1206.                     createParticle(e.clientX, e.clientY, 'πŸ’₯');
  1207.                     updateUI();
  1208.                 }
  1209.             } else if (game.selectedBuilding && game.selectedBuilding !== 'delete') {
  1210.                if (game.currentDistrict.addBuilding(gx, gy, game.selectedBuilding)) {
  1211.                    createParticle(e.clientX, e.clientY, '✨');
  1212.                     updateUI();
  1213.                 }
  1214.             } else {
  1215.                 // Select building
  1216.                 const cell = game.currentDistrict.grid[gy][gx];
  1217.                 if (cell && cell.type && cell.type !== 'tree' && cell.type !== 'rock') {
  1218.                    game.selectedObject = cell;
  1219.                     showBuildingInfo(cell);
  1220.                 } else {
  1221.                     game.selectedObject = null;
  1222.                     document.getElementById('selectionInfo').style.display = 'none';
  1223.                 }
  1224.             }
  1225.         });
  1226.  
  1227.         // Right click for worker info
  1228.         canvas.addEventListener('contextmenu', function(e) {
  1229.             e.preventDefault();
  1230.            
  1231.             const rect = canvas.getBoundingClientRect();
  1232.             const x = e.clientX - rect.left;
  1233.             const y = e.clientY - rect.top;
  1234.            
  1235.             // Check if clicking on a worker
  1236.             for (let worker of game.workers) {
  1237.                 const dist = Math.sqrt((worker.x - x) ** 2 + (worker.y - y) ** 2);
  1238.                 if (dist < 16) {
  1239.                    showWorkerInfo(worker);
  1240.                    return;
  1241.                }
  1242.            }
  1243.        });
  1244.  
  1245.        document.getElementById('zoomIn').addEventListener('click', function() {
  1246.            if (game.fractalLevel >= game.maxFractalLevel) {
  1247.                 document.getElementById('info').textContent = 'Maximum fractal depth reached!';
  1248.                 return;
  1249.             }
  1250.            
  1251.             const castle = game.currentDistrict.buildings.find(b => b.type === 'castle');
  1252.             if (castle) {
  1253.                 const key = game.fractalLevel + '-' + castle.x + '-' + castle.y;
  1254.                 const subDistrict = game.districts.get(key);
  1255.                 if (subDistrict) {
  1256.                     game.workers = [];
  1257.                     game.markets = [];
  1258.                     game.currentDistrict = subDistrict;
  1259.                     game.fractalLevel++;
  1260.                    
  1261.                     subDistrict.buildings.forEach(building => {
  1262.                         const type = buildings[building.type];
  1263.                         if (type && type.needsWorker) {
  1264.                            const house = subDistrict.buildings.find(b => b.type === 'house');
  1265.                             if (house) {
  1266.                                 const worker = new Worker(house, building);
  1267.                                 game.workers.push(worker);
  1268.                                 building.worker = worker;
  1269.                             }
  1270.                         }
  1271.                         if (building.type === 'market') {
  1272.                             game.markets.push(building);
  1273.                         }
  1274.                     });
  1275.                    
  1276.                     updateUI();
  1277.                     createParticle(window.innerWidth/2, window.innerHeight/2, '🌟');
  1278.                     document.getElementById('info').textContent = 'Entered district level ' + (game.fractalLevel + 1);
  1279.                 }
  1280.             } else {
  1281.                 document.getElementById('info').textContent = 'Build a castle first to create a sub-district!';
  1282.             }
  1283.         });
  1284.  
  1285.         document.getElementById('zoomOut').addEventListener('click', function() {
  1286.             if (game.fractalLevel === 0) {
  1287.                 document.getElementById('info').textContent = 'Already at main village level!';
  1288.                 return;
  1289.             }
  1290.            
  1291.             game.workers = [];
  1292.             game.markets = [];
  1293.             game.fractalLevel--;
  1294.            
  1295.             if (game.fractalLevel === 0) {
  1296.                 game.currentDistrict = game.districts.get('main');
  1297.             } else {
  1298.                 for (let entry of game.districts) {
  1299.                     if (entry[1].level === game.fractalLevel) {
  1300.                         game.currentDistrict = entry[1];
  1301.                         break;
  1302.                     }
  1303.                 }
  1304.             }
  1305.            
  1306.             game.currentDistrict.buildings.forEach(building => {
  1307.                 const type = buildings[building.type];
  1308.                 if (type && type.needsWorker) {
  1309.                    const house = game.currentDistrict.buildings.find(b => b.type === 'house');
  1310.                     if (house) {
  1311.                         const worker = new Worker(house, building);
  1312.                         game.workers.push(worker);
  1313.                         building.worker = worker;
  1314.                     }
  1315.                 }
  1316.                 if (building.type === 'market') {
  1317.                     game.markets.push(building);
  1318.                 }
  1319.             });
  1320.            
  1321.             updateUI();
  1322.             createParticle(window.innerWidth/2, window.innerHeight/2, '🌟');
  1323.             document.getElementById('info').textContent = 'Returned to level ' + (game.fractalLevel + 1);
  1324.         });
  1325.  
  1326.         document.getElementById('pauseBtn').addEventListener('click', function() {
  1327.             game.paused = !game.paused;
  1328.             document.getElementById('pauseBtn').textContent = game.paused ? '▢️ Play' : '⏸️ Pause';
  1329.         });
  1330.  
  1331.         document.getElementById('speedBtn').addEventListener('click', function() {
  1332.             game.speed = game.speed === 1 ? 2 : game.speed === 2 ? 3 : 1;
  1333.             document.getElementById('speedBtn').textContent = '⏩ Speed: ' + game.speed + 'x';
  1334.         });
  1335.  
  1336.         function createParticle(x, y, emoji) {
  1337.             const particle = document.createElement('div');
  1338.             particle.style.position = 'absolute';
  1339.             particle.style.left = x + 'px';
  1340.             particle.style.top = y + 'px';
  1341.             particle.style.fontSize = '24px';
  1342.             particle.style.pointerEvents = 'none';
  1343.             particle.style.zIndex = '1000';
  1344.             particle.textContent = emoji;
  1345.             particle.style.animation = 'particle-float 1s ease-out forwards';
  1346.             document.body.appendChild(particle);
  1347.            
  1348.             setTimeout(function() {
  1349.                 particle.remove();
  1350.             }, 1000);
  1351.         }
  1352.  
  1353.         // Add animation style
  1354.         const style = document.createElement('style');
  1355.         style.textContent = '@keyframes particle-float { 0% { transform: translateY(0) scale(1); opacity: 1; } 100% { transform: translateY(-50px) scale(0); opacity: 0; } }';
  1356.         document.head.appendChild(style);
  1357.  
  1358.         // Tooltip
  1359.         const tooltip = document.getElementById('tooltip');
  1360.         canvas.addEventListener('mousemove', function(e) {
  1361.             const rect = canvas.getBoundingClientRect();
  1362.             const x = Math.floor((e.clientX - rect.left) / TILE_SIZE);
  1363.             const y = Math.floor((e.clientY - rect.top) / TILE_SIZE);
  1364.            
  1365.             if (x < 0 || y < 0 || x >= MAP_WIDTH || y >= MAP_HEIGHT) {
  1366.                 tooltip.style.display = 'none';
  1367.                 return;
  1368.             }
  1369.            
  1370.             const cell = game.currentDistrict.grid[y][x];
  1371.             if (cell) {
  1372.                 let content = '';
  1373.                
  1374.                 if (cell.type === 'tree') {
  1375.                     content = '<strong>🌲 Tree</strong><br>Can be cleared for building';
  1376.                 } else if (cell.type === 'rock') {
  1377.                     content = '<strong>πŸͺ¨ Rock</strong><br>Can be cleared for building';
  1378.                 } else if (cell.type) {
  1379.                     const building = buildings[cell.type];
  1380.                     content = '<strong>' + building.tile + ' ' + cell.type.charAt(0).toUpperCase() + cell.type.slice(1) + '</strong><br>';
  1381.                    
  1382.                     if (building.isDistrict) {
  1383.                         content += 'Click "Enter District" to explore!';
  1384.                     } else if (building.provides) {
  1385.                         for (let k in building.provides) {
  1386.                             if (k === 'population') {
  1387.                                 content += 'Houses ' + building.provides[k] + ' people<br>';
  1388.                             } else {
  1389.                                 content += 'Produces: ' + building.provides[k] + ' ' + k + '/s<br>';
  1390.                             }
  1391.                         }
  1392.                        
  1393.                         if (building.needsWorker) {
  1394.                             const hasWorker = game.workers.some(w => w.workplace.id === cell.id);
  1395.                             content += 'Worker: ' + (hasWorker ? 'βœ… Assigned' : '❌ Needs worker');
  1396.                         }
  1397.                     }
  1398.                    
  1399.                     if (cell.type === 'house') {
  1400.                         content += 'Consumes: ' + (FOOD_PER_PERSON * 4).toFixed(2) + ' food/s';
  1401.                     }
  1402.                 }
  1403.                
  1404.                 if (content) {
  1405.                     tooltip.innerHTML = content;
  1406.                     tooltip.style.display = 'block';
  1407.                     tooltip.style.left = e.clientX + 10 + 'px';
  1408.                     tooltip.style.top = e.clientY - 30 + 'px';
  1409.                 } else {
  1410.                     tooltip.style.display = 'none';
  1411.                 }
  1412.             } else {
  1413.                 const tileType = game.currentDistrict.tiles[y][x];
  1414.                 if (tileType === TILES.WATER) {
  1415.                     tooltip.innerHTML = '<strong>πŸ’§ Water</strong><br>Cannot build here';
  1416.                     tooltip.style.display = 'block';
  1417.                     tooltip.style.left = e.clientX + 10 + 'px';
  1418.                     tooltip.style.top = e.clientY - 30 + 'px';
  1419.                 } else {
  1420.                     tooltip.style.display = 'none';
  1421.                 }
  1422.             }
  1423.         });
  1424.  
  1425.         canvas.addEventListener('mouseleave', function() {
  1426.             tooltip.style.display = 'none';
  1427.         });
  1428.  
  1429.         // Keyboard shortcuts
  1430.         document.addEventListener('keydown', function(e) {
  1431.             switch(e.key) {
  1432.                 case '1':
  1433.                     document.querySelector('[data-type="house"]').click();
  1434.                     break;
  1435.                 case '2':
  1436.                     document.querySelector('[data-type="farm"]').click();
  1437.                     break;
  1438.                 case '3':
  1439.                     document.querySelector('[data-type="lumber"]').click();
  1440.                     break;
  1441.                 case '4':
  1442.                     document.querySelector('[data-type="quarry"]').click();
  1443.                     break;
  1444.                 case '5':
  1445.                     document.querySelector('[data-type="market"]').click();
  1446.                     break;
  1447.                 case '6':
  1448.                     document.querySelector('[data-type="castle"]').click();
  1449.                     break;
  1450.                 case 'd':
  1451.                 case 'Delete':
  1452.                     document.querySelector('[data-type="delete"]').click();
  1453.                     break;
  1454.                 case ' ':
  1455.                     e.preventDefault();
  1456.                     document.getElementById('pauseBtn').click();
  1457.                     break;
  1458.                 case '+':
  1459.                 case '=':
  1460.                     document.getElementById('zoomIn').click();
  1461.                     break;
  1462.                 case '-':
  1463.                 case '_':
  1464.                     document.getElementById('zoomOut').click();
  1465.                     break;
  1466.                 case 'Escape':
  1467.                     game.selectedBuilding = null;
  1468.                     game.selectedObject = null;
  1469.                     document.querySelectorAll('.building-btn').forEach(b =>
  1470.                         b.classList.remove('selected')
  1471.                     );
  1472.                     document.getElementById('selectionInfo').style.display = 'none';
  1473.                     break;
  1474.             }
  1475.         });
  1476.  
  1477.         // Victory check
  1478.         function checkVictory() {
  1479.             if (game.population >= 50 && game.gold >= 2000 && !game.victoryAchieved) {
  1480.                game.victoryAchieved = true;
  1481.                 document.getElementById('info').innerHTML = '<strong style="color: #ffd700;">πŸŽ‰ Victory! You have built a thriving fractal kingdom! πŸŽ‰</strong>';
  1482.                 for (let i = 0; i < 10; i++) {
  1483.                    setTimeout(function() {
  1484.                        createParticle(
  1485.                            Math.random() * window.innerWidth,
  1486.                            Math.random() * window.innerHeight,
  1487.                            ['🎊', 'πŸŽ‰', 'πŸ‘‘', '⭐'][Math.floor(Math.random() * 4)]
  1488.                        );
  1489.                    }, i * 100);
  1490.                }
  1491.            }
  1492.        }
  1493.  
  1494.        // Game loop
  1495.        function gameLoop(currentTime) {
  1496.            const deltaTime = Math.min((currentTime - game.lastTime) / 1000, 0.1);
  1497.            game.lastTime = currentTime;
  1498.            
  1499.            if (!game.paused) {
  1500.                game.accumulator += deltaTime;
  1501.                
  1502.                while (game.accumulator >= 0.016) {
  1503.                     updateResources(0.016);
  1504.                    
  1505.                     game.workers.forEach(function(worker) {
  1506.                         worker.update(0.016);
  1507.                     });
  1508.                    
  1509.                     game.accumulator -= 0.016;
  1510.                 }
  1511.                
  1512.                 if (Math.floor(currentTime / 1000) % 5 === 0) {
  1513.                     checkVictory();
  1514.                     updateUI();
  1515.                 }
  1516.             }
  1517.            
  1518.             render();
  1519.             requestAnimationFrame(gameLoop);
  1520.         }
  1521.  
  1522.         // Initialize
  1523.         init();
  1524.         game.lastTime = performance.now();
  1525.         gameLoop(performance.now());
  1526.  
  1527.         // Initial particles
  1528.         setTimeout(function() {
  1529.             createParticle(window.innerWidth/2, 100, '⭐');
  1530.             createParticle(window.innerWidth/2 - 50, 150, '🏰');
  1531.             createParticle(window.innerWidth/2 + 50, 150, '🌟');
  1532.         }, 500);
  1533.  
  1534.         console.log('🏰 Fractal Village initialized! Build your recursive kingdom!');
  1535.     </script>
  1536. </body>
  1537. </html>
  1538.  
Add Comment
Please, Sign In to add comment