economy.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  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. def __init__(self, rows=10, cols=10):
  8. self.shape = shape = (rows, cols)
  9. self.lc_pops = np.zeros(shape, dtype=np.float32)
  10. self.mc_pops = np.zeros(shape, dtype=np.float32)
  11. self.uc_pops = np.zeros(shape, dtype=np.float32)
  12. self.lc_starvation = np.zeros(shape, dtype=np.bool)
  13. self.mc_starvation = np.zeros(shape, dtype=np.bool)
  14. self.uc_starvation = np.zeros(shape, dtype=np.bool)
  15. self.any_starvation = np.zeros(shape, dtype=np.bool)
  16. self.food_rate = np.zeros(shape, dtype=np.float32)
  17. self.food_production_capacity = np.zeros(shape, dtype=np.float32)
  18. self.food_production = np.zeros(shape, dtype=np.float32)
  19. self.food_consumption = np.zeros(shape, dtype=np.float32)
  20. self.food_stored = np.zeros(shape, dtype=np.float32)
  21. self.food_stored_capacity = np.zeros(shape, dtype=np.float32)
  22. self.trade_distance = np.zeros(shape, dtype=np.float32)
  23. self.trade_range = np.full(shape, 5)
  24. self.trade_value = np.zeros(shape, dtype=np.float32)
  25. self.export_partner = np.full((*shape, 2), 0)
  26. def init(self, verkolst):
  27. self.shape = (verkolst.width, verkolst.height)
  28. self.lc_pops = np.full(self.shape, 25, dtype=np.float32)
  29. self.mc_pops = np.full(self.shape, 0, dtype=np.float32)
  30. self.uc_pops = np.full(self.shape, 0, dtype=np.float32)
  31. self.lc_starvation = np.full_like(self.lc_pops, False, dtype=np.bool)
  32. self.mc_starvation = np.full_like(self.lc_pops, False, dtype=np.bool)
  33. self.uc_starvation = np.full_like(self.lc_pops, False, dtype=np.bool)
  34. self.any_starvation = np.full_like(self.lc_pops, False, dtype=np.bool)
  35. # Lets calculate the base food production
  36. terrain = verkolst["terrain"]
  37. food_rate = np.full(self.shape, 0, dtype=np.float32)
  38. food_capacity = np.full(self.shape, 0, dtype=np.float32)
  39. trade_distance = np.full(self.shape, 0, dtype=np.float32)
  40. for i, j in product(range(self.shape[0]), range(self.shape[1])):
  41. food_rate[i][j] = terrain.cells[i][j].tile.properties["food_rate"]
  42. food_capacity[i][j] = terrain.cells[i][j].tile.properties["food_capacity"]
  43. trade_distance[i][j] = terrain.cells[i][j].tile.properties["trade_distance"]
  44. self.food_rate = food_rate
  45. self.food_production_capacity = food_capacity
  46. self.food_production = np.full_like(self.lc_pops, 0)
  47. self.update_food_production()
  48. self.food_consumption = 5 * self.uc_pops + 2 * self.mc_pops + 1 * self.lc_pops
  49. self.food_stored = 2 * self.food_production
  50. self.food_stored_capacity = np.full_like(self.lc_pops, 5000)
  51. self.trade_distance = trade_distance
  52. self.trade_range = np.full_like(self.lc_pops, 5)
  53. self.update_trade_value()
  54. self.export_partner = np.full((*self.shape, 2), 0)
  55. self.update_export_partners()
  56. def load(self, save_dir):
  57. with open(join(save_dir, "pops.json"), "r") as f:
  58. obs = json.load(f)
  59. self.lc_pops = pickle.loads(obs["lc_pops"])
  60. self.mc_pops = pickle.loads(obs["mc_pops"])
  61. self.uc_pops = pickle.loads(obs["uc_pops"])
  62. def dump(self, save_dir):
  63. with open(join(save_dir, "pops.json"), "w") as f:
  64. json.dump(
  65. {
  66. "lc_pops": self.lc_pops.dumps(),
  67. "mc_pops": self.mc_pops.dumps(),
  68. "uc_pops": self.uc_pops.dumps(),
  69. },
  70. f,
  71. )
  72. def update_food_production(self):
  73. (self.food_rate * np.floor(self.lc_pops)).clip(
  74. min=0, max=self.food_production_capacity, out=self.food_production
  75. )
  76. def update_trade_value(self):
  77. self.trade_value = self.lc_pops*1 + self.mc_pops*5 + self.uc_pops*20
  78. def update_export_partners(self):
  79. from app.trade.trade_utils import update_export_partner
  80. update_export_partner(self.trade_range, self.trade_distance, self.trade_value, self.export_partner)
  81. def eat_food(self):
  82. # First, add any produced food to the stockpile
  83. self.food_stored += self.food_production
  84. # Next eat the food from uc->mc->lc marking starvation as it occurs
  85. self.food_stored -= np.floor(self.uc_pops) * 5
  86. self.uc_starvation = self.food_stored <= 0
  87. self.food_stored -= np.floor(self.mc_pops) * 2
  88. self.mc_starvation = self.food_stored <= 0
  89. self.food_stored -= np.floor(self.lc_pops) * 1
  90. self.lc_starvation = self.food_stored <= 0
  91. self.any_starvation = np.logical_or(
  92. self.lc_starvation, np.logical_or(self.mc_starvation, self.uc_starvation)
  93. )
  94. # Finally, clip the amount of food stored to between 0 and the food storage capacity
  95. self.food_stored.clip(
  96. min=0, max=self.food_stored_capacity, out=self.food_stored
  97. )
  98. def redistribute_classes(self):
  99. total_pops = self.lc_pops + self.mc_pops + self.uc_pops
  100. current_proportions = np.stack([self.lc_pops, self.mc_pops, self.uc_pops]) / (
  101. total_pops
  102. )
  103. target_proportions = np.stack([
  104. np.full_like(self.lc_pops, 0.8),
  105. np.full_like(self.lc_pops, 0.15),
  106. np.full_like(self.lc_pops, 0.05),
  107. ])
  108. target_proportions[0, self.any_starvation] = 1
  109. target_proportions[1, self.any_starvation] = 0
  110. target_proportions[2, self.any_starvation] = 0
  111. new_proportions = current_proportions + (target_proportions - current_proportions) * 0.1
  112. self.lc_pops = total_pops * new_proportions[0]
  113. self.mc_pops = total_pops * new_proportions[1]
  114. self.uc_pops = total_pops * new_proportions[2]
  115. def make_babies(self):
  116. food_stored_proportion = self.food_stored / self.food_stored_capacity
  117. repro_rate = 1 + 0.10 * food_stored_proportion
  118. repro_rate[self.any_starvation] = 1.0
  119. repro_rate[self.lc_starvation] = 0.95
  120. np.multiply(self.lc_pops, repro_rate, out=self.lc_pops, casting="unsafe")
  121. repro_rate = 1 + 0.08 * food_stored_proportion
  122. repro_rate[self.any_starvation] = 1.0
  123. repro_rate[self.mc_starvation] = 0.95
  124. np.multiply(self.mc_pops, repro_rate, out=self.mc_pops, casting="unsafe")
  125. repro_rate = 1 + 0.06 * food_stored_proportion
  126. repro_rate[self.any_starvation] = 1.0
  127. repro_rate[self.uc_starvation] = 0.95
  128. np.multiply(self.uc_pops, repro_rate, out=self.uc_pops, casting="unsafe")
  129. self.food_consumption = (
  130. 5 * np.floor(self.uc_pops)
  131. + 2 * np.floor(self.mc_pops)
  132. + 1 * np.floor(self.lc_pops)
  133. )
  134. def process_turn(self):
  135. self.eat_food()
  136. self.redistribute_classes()
  137. self.make_babies()
  138. self.update_food_production()
  139. self.update_trade_value()
  140. self.update_export_partners()