Environment.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. import random
  2. from pypdevs.simulator import AtomicDEVS
  3. class Phase:
  4. IDLE = 0
  5. RESOLVING = 1
  6. PHASES = ["IDLE", "RESOLVING"]
  7. class EnvironmentState:
  8. def __init__(self, dims: tuple[int, int], regrowth_time: int, rng: random.Random):
  9. self.phase = Phase.IDLE
  10. self.width, self.height = dims
  11. self.regrowth_time = regrowth_time
  12. total_cells = self.width * self.height
  13. self.growing_grass = [
  14. rng.randint(1, regrowth_time) if rng.random() >= 0.5 else 0
  15. for _ in range(total_cells)
  16. ]
  17. self.sheep_grid = {}
  18. self.animal_positions = {}
  19. self.dead_animals = []
  20. self.successful_eaters = set()
  21. def __repr__(self):
  22. # alive_sheep = sum(len(cell) for row in self.sheep_grid for cell in row)
  23. # alive_wolves = sum(len(cell) for row in self.wolf_grid for cell in row)
  24. return f"[Environment | Phase: {PHASES[self.phase]}]"
  25. class Environment(AtomicDEVS):
  26. def __init__(self, dims: tuple[int, int], regrowth_time: int, rng: random.Random):
  27. super().__init__("Environment")
  28. self.rng = rng
  29. self.width, self.height = dims
  30. self.state = EnvironmentState(dims, regrowth_time, rng)
  31. self.animal_born = self.addInPort("Animal Born")
  32. self.animal_died = self.addInPort("Animal Died")
  33. self.sheep_move = self.addInPort("Sheep Move")
  34. self.wolf_move = self.addInPort("Wolf Move")
  35. self.out_broadcast = self.addOutPort("Animal Broadcast")
  36. def extTransition(self, inputs):
  37. if self.animal_born in inputs:
  38. for species, aid, pos in inputs[self.animal_born]:
  39. self._register_animal(aid, species, pos)
  40. if self.animal_died in inputs:
  41. for species, aid, pos in inputs[self.animal_died]:
  42. self._deregister_animal(aid, species, pos)
  43. sheep_moves = inputs.get(self.sheep_move, [])
  44. wolf_moves = inputs.get(self.wolf_move, [])
  45. if sheep_moves or wolf_moves:
  46. self._process_sheep_turn(sheep_moves)
  47. self._process_wolf_turn(wolf_moves)
  48. self._grow_grass()
  49. self.state.phase = Phase.RESOLVING
  50. return self.state
  51. def intTransition(self):
  52. if self.state.phase == Phase.RESOLVING:
  53. self.state.successful_eaters = set()
  54. self.state.phase = Phase.IDLE
  55. else:
  56. self._grow_grass()
  57. return self.state
  58. def timeAdvance(self):
  59. return 0.0 if self.state.phase == Phase.RESOLVING else 1.0
  60. def outputFnc(self):
  61. out_bag = {}
  62. if self.state.phase == Phase.RESOLVING:
  63. out_bag[self.out_broadcast] = [self.state.successful_eaters]
  64. return out_bag
  65. def modelTransition(self, state):
  66. if self.state.dead_animals:
  67. if "Dead" not in state:
  68. state["Dead"] = []
  69. state["Dead"].extend(self.state.dead_animals)
  70. self.state.dead_animals.clear()
  71. return True
  72. return False
  73. def _register_animal(self, aid: int, species: int, pos: int):
  74. self.state.animal_positions[aid] = pos
  75. if species == 0: # SHEEP
  76. if pos not in self.state.sheep_grid:
  77. self.state.sheep_grid[pos] = set()
  78. self.state.sheep_grid[pos].add(aid)
  79. def _deregister_animal(self, aid: int, species: int, pos: int):
  80. if aid in self.state.animal_positions:
  81. del self.state.animal_positions[aid]
  82. if species == 0 and pos in self.state.sheep_grid: # SHEEP
  83. self.state.sheep_grid[pos].discard(aid)
  84. if not self.state.sheep_grid[pos]:
  85. del self.state.sheep_grid[pos]
  86. def _move_animal_on_grid(self, aid: int, species: int, new_pos: int):
  87. old_pos = self.state.animal_positions.get(aid)
  88. if old_pos == new_pos:
  89. return
  90. if old_pos and species == 0: # SHEEP
  91. self.state.sheep_grid[old_pos].discard(aid)
  92. if not self.state.sheep_grid[old_pos]:
  93. del self.state.sheep_grid[old_pos]
  94. self.state.animal_positions[aid] = new_pos
  95. if species == 0: # SHEEP
  96. if new_pos not in self.state.sheep_grid:
  97. self.state.sheep_grid[new_pos] = set()
  98. self.state.sheep_grid[new_pos].add(aid)
  99. def _process_sheep_turn(self, sheep_moves: list):
  100. self.rng.shuffle(sheep_moves)
  101. for aid, new_pos in sheep_moves:
  102. if aid not in self.state.animal_positions:
  103. continue
  104. self._move_animal_on_grid(aid, 0, new_pos)
  105. if self.state.growing_grass[new_pos] == 0:
  106. self.state.growing_grass[new_pos] = self.state.regrowth_time
  107. self.state.successful_eaters.add(aid)
  108. def _process_wolf_turn(self, wolf_moves: list):
  109. self.rng.shuffle(wolf_moves)
  110. for aid, new_pos in wolf_moves:
  111. if aid not in self.state.animal_positions:
  112. continue
  113. self._move_animal_on_grid(aid, 1, new_pos)
  114. sheep_at_pos = self.state.sheep_grid.get(new_pos)
  115. if sheep_at_pos:
  116. victim_id = self.rng.choice(sorted(list(sheep_at_pos)))
  117. self._deregister_animal(victim_id, 0, new_pos)
  118. self.state.dead_animals.append(victim_id)
  119. self.state.successful_eaters.add(aid)
  120. def _grow_grass(self):
  121. for pos in range(len(self.state.growing_grass)):
  122. if self.state.growing_grass[pos] > 0:
  123. self.state.growing_grass[pos] -= 1