bouncingballs_factored.py 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. from pypdevs.DEVS import AtomicDEVS, CoupledDEVS
  2. from pypdevs.simulator import Simulator
  3. from pypdevs.infinity import INFINITY
  4. import random
  5. import math
  6. import os
  7. import Tkinter as tk
  8. class Canvas:
  9. def __init__(self, width, height):
  10. self.width = width
  11. self.height = height
  12. class Ball(AtomicDEVS):
  13. def __init__(self, ball_id, canvas, framerate):
  14. AtomicDEVS.__init__(self, "Ball[%i]" % ball_id)
  15. ''' Ports '''
  16. self.collision_detect = self.addInPort("colision_detect")
  17. self.position_out = self.addOutPort("position_out")
  18. self.ball_spawner_comm = self.addOutPort("ball_spawner")
  19. self.color_out = self.addOutPort("color_out")
  20. ''' Parameters '''
  21. self.framerate = framerate
  22. self.canvas = canvas
  23. self.ball_id = ball_id
  24. ''' State Initialization '''
  25. self.r = random.uniform(5.0, 20.0)
  26. self.vel = {'x': random.random() * 5.0 - 1.0, 'y': random.random() * 5.0 - 1.0}
  27. self.curr_state = "bouncing" # stateset = ('bouncing', 'spawning', 'selected', 'deleting')
  28. self.color = "red"
  29. self.prev_color = "red"
  30. self.pos = None
  31. self.time_passed = 0
  32. self.collision_detected = False
  33. self.remaining = self.framerate
  34. self.initialized = False
  35. def timeAdvance(self):
  36. # immediately output updated color
  37. if self.prev_color != self.color:
  38. return 0.0
  39. # framerate or waiting in selected mode
  40. elif self.curr_state in ("bouncing", "spawning", "selected"):
  41. return self.remaining
  42. elif self.curr_state == "deleting":
  43. return INFINITY
  44. def outputFnc(self):
  45. output = {}
  46. if self.pos and self.curr_state in ("bouncing", "spawning"):
  47. output[self.position_out] = [(self.ball_id, self.pos, self.r)]
  48. if self.collision_detected and self.state != "spawning":
  49. output[self.ball_spawner_comm] = ["spawn_ball"]
  50. if self.curr_state == "selected" and self.prev_color == self.color:
  51. output[self.position_out] = [("delete", self.ball_id)]
  52. if self.color != self.prev_color:
  53. output[self.color_out] = [(self.ball_id, self.color)]
  54. return output
  55. def intTransition(self):
  56. # initialize position and velocity
  57. if not self.initialized:
  58. x = random.uniform(0, self.canvas.width - (self.r * 2 + 1))
  59. y = random.uniform(0, self.canvas.height - (self.r * 2 + 1))
  60. self.pos = (x, y)
  61. self.initialized = True
  62. else:
  63. if random.random() <= 0.008 and not self.curr_state == "selected":
  64. self.color = "yellow"
  65. self.curr_state = "selected"
  66. self.remaining = 2.5
  67. elif self.curr_state == "selected" and self.prev_color == self.color:
  68. self.curr_state = "deleting"
  69. elif self.prev_color != self.color:
  70. self.prev_color = self.color
  71. else:
  72. if self.time_passed < 1 and self.curr_state in ("bouncing", "spawning"):
  73. self.time_passed += self.framerate
  74. if self.time_passed >= 1:
  75. self.color = "black"
  76. self.curr_state = "bouncing"
  77. self.time_passed = 0
  78. x = self.pos[0]
  79. y = self.pos[1]
  80. if x <= 0 or x + (self.r * 2) >= self.canvas.width:
  81. self.vel['x'] = -self.vel['x']
  82. if y <= 0 or y + (self.r * 2) >= self.canvas.height:
  83. self.vel['y'] = -self.vel['y']
  84. self.pos = (self.pos[0] + self.vel['x'], self.pos[1] + self.vel['y'])
  85. if self.collision_detected:
  86. self.curr_state = "spawning"
  87. self.color = "blue"
  88. self.remaining = 0.03
  89. self.collision_detected = False
  90. def extTransition(self, inputs):
  91. self.remaining -= self.elapsed
  92. if self.collision_detect in inputs and self.curr_state == "bouncing":
  93. self.collision_detected = True
  94. def modelTransition(self, state):
  95. if self.curr_state == "deleting":
  96. state['to_delete'] = self
  97. return True
  98. class BallSpawner(AtomicDEVS):
  99. def __init__(self, canvas):
  100. AtomicDEVS.__init__(self, "BallSpawner")
  101. self.request = self.addInPort("request")
  102. self.canvas = canvas
  103. self.id_ctr = 0
  104. self.new_ball = None
  105. self.framerate = 0.03
  106. def timeAdvance(self):
  107. return 1
  108. def intTransition(self):
  109. self.new_ball = Ball(self.id_ctr, self.canvas, self.framerate)
  110. self.id_ctr += 1
  111. def extTransition(self, inputs):
  112. if self.request in inputs:
  113. self.new_ball = Ball(self.id_ctr, self.canvas, self.framerate)
  114. self.id_ctr += 1
  115. def modelTransition(self, state):
  116. if self.new_ball:
  117. state['new_ball'] = self.new_ball
  118. return True
  119. return False
  120. class PositionManager(AtomicDEVS):
  121. def __init__(self):
  122. AtomicDEVS.__init__(self, "PositionManager")
  123. self.balls_in = self.addInPort("balls_in")
  124. self.balls_out = {}
  125. self.positions_out = self.addOutPort("positions_out")
  126. self.collisions = set([])
  127. self.positions = {}
  128. self.remaining = 0.03
  129. def timeAdvance(self):
  130. return 0 if self.collisions else self.remaining
  131. def extTransition(self, inputs):
  132. self.remaining -= self.elapsed
  133. if self.balls_in in inputs:
  134. input_bag = inputs[self.balls_in]
  135. for i in input_bag:
  136. if i[0] == "delete":
  137. if i[1] in self.positions:
  138. del self.positions[i[1]]
  139. else:
  140. self.positions[i[0]] = (i[1], i[2])
  141. for k1, v1 in self.positions.iteritems():
  142. for k2, v2 in self.positions.iteritems():
  143. if k1 != k2:
  144. dx = v1[0][0] - v2[0][0]
  145. dy = v1[0][1] - v2[0][1]
  146. distance = math.sqrt(dx**2 + dy**2)
  147. if distance < v1[1] + v2[1]:
  148. self.collisions.add(k1)
  149. self.collisions.add(k2)
  150. def intTransition(self):
  151. self.collisions = set([])
  152. self.remaining = 0.03
  153. def outputFnc(self):
  154. output = {}
  155. if self.collisions:
  156. output = {self.balls_out[ball_id]: ["collision_detected"] for ball_id in self.collisions}
  157. output[self.positions_out] = [self.positions]
  158. return output
  159. class MyTracer(AtomicDEVS):
  160. def __init__(self):
  161. AtomicDEVS.__init__(self, "MyTracer")
  162. self.positions_in = self.addInPort("positions_in")
  163. self.color_in = self.addInPort("color_in")
  164. self.current_time = 0.0
  165. def timeAdvance(self):
  166. return INFINITY
  167. def extTransition(self, inputs):
  168. self.current_time += self.elapsed
  169. with open('sim_output', 'a') as f:
  170. f.write('time %f\n' % self.current_time)
  171. if self.positions_in in inputs:
  172. f.write('positions\n')
  173. positions = inputs[self.positions_in][0]
  174. for k, v in positions.iteritems():
  175. f.write('%i %f %f %f\n' % (k, v[0][0], v[0][1], v[1]))
  176. f.write('endpositions\n')
  177. if self.color_in in inputs:
  178. f.write('color\n')
  179. for color in inputs[self.color_in]:
  180. f.write('%i %s\n' % (color[0], color[1]))
  181. f.write('endcolor\n')
  182. class Field(CoupledDEVS):
  183. def __init__(self, width, height):
  184. CoupledDEVS.__init__(self, "Field")
  185. self.canvas = Canvas(width, height)
  186. self.ball_spawner = self.addSubModel(BallSpawner(self.canvas))
  187. self.position_manager = self.addSubModel(PositionManager())
  188. self.tracer = self.addSubModel(MyTracer())
  189. self.connectPorts(self.position_manager.positions_out, self.tracer.positions_in)
  190. def modelTransition(self, state):
  191. if 'new_ball' in state:
  192. new_ball = state['new_ball']
  193. self.addSubModel(new_ball)
  194. pos_mgr_port = self.position_manager.addOutPort("balls_out[%s]" % new_ball.ball_id)
  195. ''' TODO: can I just change the state like that here? -> Probably move to Ball '''
  196. self.position_manager.balls_out[new_ball.ball_id] = pos_mgr_port
  197. self.connectPorts(new_ball.position_out, self.position_manager.balls_in)
  198. self.connectPorts(pos_mgr_port, new_ball.collision_detect)
  199. self.connectPorts(new_ball.ball_spawner_comm, self.ball_spawner.request)
  200. self.connectPorts(new_ball.color_out, self.tracer.color_in)
  201. del state['new_ball']
  202. if 'to_delete' in state:
  203. self.removeSubModel(state['to_delete'])
  204. del state['to_delete']
  205. return False
  206. if __name__ == '__main__':
  207. try:
  208. os.remove('sim_output')
  209. except:
  210. pass
  211. random.seed(1)
  212. ''' instantiate DEVS model '''
  213. model = Field(800, 600)
  214. ''' simulator setup '''
  215. sim = Simulator(model)
  216. sim.setDSDEVS(True)
  217. sim.setTerminationTime(60)
  218. #sim.setVerbose("myOutputFile")
  219. ''' run simulation + visualization '''
  220. sim.simulate()