economy.py 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. import json
  2. import pickle
  3. from os.path import join
  4. from itertools import product
  5. import numpy as np
  6. class Resources:
  7. base_trade_range = 2
  8. def __init__(self, rows=10, cols=10):
  9. self.shape = shape = (rows, cols)
  10. self.lc_pops = np.zeros(shape, dtype=np.int32)
  11. self.mc_pops = np.zeros(shape, dtype=np.int32)
  12. self.uc_pops = np.zeros(shape, dtype=np.int32)
  13. self.pops_cap = np.zeros(shape, dtype=np.int32)
  14. self.lc_starvation = np.zeros(shape, dtype=np.bool)
  15. self.mc_starvation = np.zeros(shape, dtype=np.bool)
  16. self.uc_starvation = np.zeros(shape, dtype=np.bool)
  17. self.any_starvation = np.zeros(shape, dtype=np.bool)
  18. self.food_rate = np.zeros(shape, dtype=np.float32)
  19. self.food_production_cap = np.zeros(shape, dtype=np.float32)
  20. self.food_production = np.zeros(shape, dtype=np.float32)
  21. self.food_imports = np.zeros(shape, dtype=np.float32)
  22. self.food_consumption = np.zeros(shape, dtype=np.float32)
  23. self.food_stored = np.zeros(shape, dtype=np.float32)
  24. self.food_stored_capacity = np.zeros(shape, dtype=np.float32)
  25. self.trade_impedance = np.zeros(shape, dtype=np.float32)
  26. self.trade_range = np.full(shape, self.base_trade_range, dtype=np.float32)
  27. self.trade_value = np.zeros(shape, dtype=np.float32)
  28. self.trade_modifier = np.zeros(shape, dtype=np.float32)
  29. self.export_partner = np.full((*shape, 2), 0, dtype=np.int32)
  30. self.goods_production = {
  31. 'stone': np.full(shape, 0, dtype=np.float32),
  32. 'wood': np.full(shape, 0, dtype=np.float32),
  33. }
  34. def init(self, verkolst):
  35. self.shape = (verkolst.width, verkolst.height)
  36. self.lc_pops = np.full(self.shape, 25, dtype=np.int32)
  37. self.mc_pops = np.full(self.shape, 0, dtype=np.int32)
  38. self.uc_pops = np.full(self.shape, 0, dtype=np.int32)
  39. self.lc_starvation = np.full_like(self.lc_pops, False, dtype=np.bool)
  40. self.mc_starvation = np.full_like(self.lc_pops, False, dtype=np.bool)
  41. self.uc_starvation = np.full_like(self.lc_pops, False, dtype=np.bool)
  42. self.any_starvation = np.full_like(self.lc_pops, False, dtype=np.bool)
  43. terrain = verkolst["terrain"]
  44. buildings = verkolst["buildings"]
  45. food_rate = np.full(self.shape, 0, dtype=np.float32)
  46. food_production_cap = np.full(self.shape, 0, dtype=np.float32)
  47. trade_impedance = np.full(self.shape, 0, dtype=np.float32)
  48. stone_production = np.full(self.shape, 0, dtype=np.float32)
  49. wood_production = np.full(self.shape, 0, dtype=np.float32)
  50. trade_modifier = np.full(self.shape, 0, dtype=np.float32)
  51. pops_cap = np.full(self.shape, 0, dtype=np.float32)
  52. for i, j in product(range(self.shape[0]), range(self.shape[1])):
  53. food_rate[i][j] = terrain.cells[i][j].tile.properties["food_rate"]
  54. food_production_cap[i][j] = terrain.cells[i][j].tile.properties["food_production_cap"]
  55. trade_impedance[i][j] = terrain.cells[i][j].tile.properties["trade_impedance"]
  56. trade_modifier[i][j] = buildings.cells[i][j].tile.properties["trade_modifier"]
  57. pops_cap[i][j] = terrain.cells[i][j].tile.properties["pops_cap"]
  58. stone_production[i][j] = terrain.cells[i][j].tile.properties["stone_production"]
  59. wood_production[i][j] = terrain.cells[i][j].tile.properties["wood_production"]
  60. self.goods_production = {
  61. 'stone': stone_production,
  62. 'wood': wood_production,
  63. }
  64. self.food_rate = food_rate
  65. self.food_production_cap = food_production_cap
  66. self.food_production = np.full_like(self.lc_pops, 0)
  67. self.food_imports = np.full_like(self.lc_pops, 0)
  68. self.update_food_production()
  69. self.food_consumption = 5 * self.uc_pops + 2 * self.mc_pops + 1 * self.lc_pops
  70. self.food_stored = 2 * self.food_production
  71. self.food_stored_capacity = np.full_like(self.lc_pops, 5000)
  72. self.trade_impedance = trade_impedance
  73. self.trade_range = np.full_like(self.lc_pops, self.base_trade_range, dtype=np.float32)
  74. self.trade_modifier = trade_modifier
  75. self.export_partner = np.full((*self.shape, 2), 0)
  76. # self.update_export_partners()
  77. self.pops_cap = pops_cap
  78. # Now process a single turn to sync up everything
  79. self.process_turn()
  80. def load(self, save_dir):
  81. with open(join(save_dir, "pops.json"), "r") as f:
  82. obs = json.load(f)
  83. self.lc_pops = pickle.loads(obs["lc_pops"])
  84. self.mc_pops = pickle.loads(obs["mc_pops"])
  85. self.uc_pops = pickle.loads(obs["uc_pops"])
  86. def dump(self, save_dir):
  87. with open(join(save_dir, "pops.json"), "w") as f:
  88. json.dump(
  89. {
  90. "lc_pops": self.lc_pops.dumps(),
  91. "mc_pops": self.mc_pops.dumps(),
  92. "uc_pops": self.uc_pops.dumps(),
  93. },
  94. f,
  95. )
  96. def update_food_production(self):
  97. (self.food_rate * self.lc_pops).clip(
  98. min=0, max=self.food_production_cap, out=self.food_production
  99. )
  100. def update_trade_value(self):
  101. self.trade_value = (self.lc_pops*1 + self.mc_pops*5 + self.uc_pops*20).astype(np.float32) * self.trade_modifier
  102. def update_export_partners(self):
  103. from app.trade.trade_utils import update_export_partner
  104. update_export_partner(self.trade_range, self.trade_impedance, self.trade_value, self.export_partner)
  105. def eat_food(self):
  106. # from app.trade.trade_utils import share_food
  107. # First, add any produced food to the stockpile
  108. self.food_stored += self.food_production
  109. # share_food(self.food_production, self.food_consumption, self.food_stored, self.food_stored_capacity,
  110. # self.export_partner, 0.1)
  111. # Next eat the food from uc->mc->lc marking starvation as it occurs
  112. self.food_stored -= self.uc_pops * 5
  113. self.uc_starvation = self.food_stored <= 0
  114. self.food_stored -= self.mc_pops * 2
  115. self.mc_starvation = self.food_stored <= 0
  116. self.food_stored -= self.lc_pops * 1
  117. self.lc_starvation = self.food_stored <= 0
  118. self.any_starvation = np.any([self.lc_starvation, self.mc_starvation, self.uc_starvation], axis=0)
  119. # Finally, clip the amount of food stored to between 0 and the food storage capacity
  120. self.food_stored.clip(
  121. min=0, max=self.food_stored_capacity, out=self.food_stored
  122. )
  123. def redistribute_classes(self):
  124. total_pops = self.lc_pops + self.mc_pops + self.uc_pops
  125. current_proportions = np.divide(np.stack([self.lc_pops, self.mc_pops, self.uc_pops]), total_pops)
  126. np.nan_to_num(current_proportions, copy=False, nan=0, posinf=0)
  127. target_proportions = np.stack([
  128. np.full_like(self.lc_pops, 0.8, dtype=np.float32),
  129. np.full_like(self.lc_pops, 0.15, dtype=np.float32),
  130. np.full_like(self.lc_pops, 0.05, dtype=np.float32),
  131. ])
  132. target_proportions[0, self.any_starvation] = 1
  133. target_proportions[1, self.any_starvation] = 0
  134. target_proportions[2, self.any_starvation] = 0
  135. new_proportions = current_proportions + (target_proportions - current_proportions) * 0.1
  136. self.lc_pops = np.ceil(np.multiply(total_pops, new_proportions[0])).astype(np.int32)
  137. self.mc_pops = np.ceil(np.multiply(total_pops, new_proportions[1])).astype(np.int32)
  138. self.uc_pops = np.ceil(np.multiply(total_pops, new_proportions[2])).astype(np.int32)
  139. def make_babies(self):
  140. food_stored_proportion = self.food_stored / self.food_stored_capacity
  141. repro_rate = 0.05 * food_stored_proportion
  142. repro_rate[self.any_starvation] = 0.0
  143. repro_rate[self.lc_starvation] = -0.05
  144. np.add(self.lc_pops, np.sign(repro_rate) * np.random.binomial(self.lc_pops, np.abs(repro_rate)),
  145. out=self.lc_pops, casting='unsafe')
  146. repro_rate = 0.02 * food_stored_proportion
  147. repro_rate[self.any_starvation] = 0.0
  148. repro_rate[self.mc_starvation] = -0.05
  149. np.add(self.mc_pops, np.sign(repro_rate) * np.random.binomial(self.mc_pops, np.abs(repro_rate)),
  150. out=self.mc_pops, casting='unsafe')
  151. repro_rate = 0.01 * food_stored_proportion
  152. repro_rate[self.any_starvation] = 0.0
  153. repro_rate[self.uc_starvation] = -0.05
  154. np.add(self.uc_pops, np.sign(repro_rate) * np.random.binomial(self.uc_pops, np.abs(repro_rate)),
  155. out=self.uc_pops, casting='unsafe')
  156. overpop_fraction = np.divide(self.pops_cap, self.lc_pops+self.mc_pops+self.uc_pops)
  157. np.nan_to_num(overpop_fraction, copy=False, nan=1.0, posinf=1.0, neginf=1.0)
  158. overpop_fraction.clip(max=1.0, out=overpop_fraction)
  159. self.lc_pops = np.multiply(self.lc_pops, overpop_fraction).astype(np.int32)
  160. self.mc_pops = np.multiply(self.mc_pops, overpop_fraction).astype(np.int32)
  161. self.uc_pops = np.multiply(self.uc_pops, overpop_fraction).astype(np.int32)
  162. self.food_consumption = 5 * self.uc_pops + 2 * self.mc_pops + 1 * self.lc_pops
  163. def process_turn(self):
  164. self.eat_food()
  165. self.redistribute_classes()
  166. self.make_babies()
  167. self.update_food_production()
  168. self.update_trade_value()
  169. self.update_export_partners()