+ "source": "import random\nimport numpy\nimport b2d\nimport math\nimport random\n\nimport b2d\nimport asyncio\nfrom b2d.testbed.backend.gui_base import GuiBase\nimport functools\nimport IPython\nfrom b2d.testbed import TestbedBase\nimport time\nfrom pyb2d_jupyterlite_backend.async_jupyter_gui import JupyterAsyncGui\n\nimport random\nclass Billiard(TestbedBase):\n\n name = \"Billiard\"\n\n def __init__(self, settings=None):\n super(Billiard, self).__init__(gravity=(0, 0), settings=settings)\n dimensions = [30, 50]\n self.dimensions = dimensions\n\n # the outer box\n box_shape = b2d.ChainShape()\n box_shape.create_loop(\n [\n (0, 0),\n (0, dimensions[1]),\n (dimensions[0], dimensions[1]),\n (dimensions[0], 0),\n ]\n )\n self.ball_radius = 1\n box = self.world.create_static_body(\n position=(0, 0), fixtures=b2d.fixture_def(shape=box_shape, friction=0)\n )\n\n self.place_balls()\n self.place_pockets()\n\n # mouse interaction\n self._selected_ball = None\n self._selected_ball_pos = None\n self._last_pos = None\n\n # balls to be destroyed in the next step\n # since they are in the pocket\n self._to_be_destroyed = []\n\n def place_pockets(self):\n pocket_radius = 1\n self.pockets = []\n\n def place_pocket(position):\n pocket_shape = b2d.circle_shape(radius=pocket_radius / 3)\n pocket = self.world.create_static_body(\n position=position,\n fixtures=b2d.fixture_def(shape=pocket_shape, is_sensor=True),\n user_data=(\"pocket\", None),\n )\n self.pockets.append(pocket)\n\n d = pocket_radius / 2\n\n place_pocket(position=(0 + d, 0 + d))\n place_pocket(position=(self.dimensions[0] - d, 0 + d))\n\n place_pocket(position=(0 + d, self.dimensions[1] / 2))\n place_pocket(position=(self.dimensions[0] - d, self.dimensions[1] / 2))\n\n place_pocket(position=(0 + d, self.dimensions[1] - d))\n place_pocket(position=(self.dimensions[0] - d, self.dimensions[1] - d))\n\n def place_balls(self):\n self.balls = []\n\n base_colors = [\n (1, 1, 0),\n (0, 0, 1),\n (1, 0, 0),\n (1, 0, 1),\n (1, 0.6, 0),\n (0, 1, 0),\n (0.7, 0.4, 0.4),\n ]\n colors = []\n for color in base_colors:\n # ``full`` ball\n colors.append((color, color))\n # ``half`` ball (half white)\n colors.append((color, (1, 1, 1)))\n\n random.shuffle(colors)\n colors.insert(4, ((0, 0, 0), (0, 0, 0))) # black\n\n n_y = 5\n c_x = self.dimensions[0] / 2\n diameter = (self.ball_radius * 2) * 1.01\n\n bi = 0\n for y in range(n_y):\n\n py = y * diameter * 0.5 * math.sqrt(3)\n n_x = y + 1\n ox = diameter * (n_y - y) / 2\n for x in range(y + 1):\n position = (x * diameter + 10 + ox, py + 30)\n self.create_billard_ball(position=position, color=colors[bi])\n bi += 1\n\n self.create_billard_ball(position=(c_x, 10), color=((1, 1, 1), (1, 1, 1)))\n\n def create_billard_ball(self, position, color):\n\n ball = self.world.create_dynamic_body(\n position=position,\n fixtures=b2d.fixture_def(\n shape=b2d.circle_shape(radius=self.ball_radius),\n density=1.0,\n restitution=0.8,\n ),\n linear_damping=0.8,\n user_data=(\"ball\", color),\n fixed_rotation=True,\n )\n self.balls.append(ball)\n\n def begin_contact(self, contact):\n body_a = contact.body_a\n body_b = contact.body_b\n\n ud_a = body_a.user_data\n ud_b = body_b.user_data\n if ud_a is None or ud_b is None:\n return\n\n if ud_b[0] == \"ball\":\n body_a, body_b = body_b, body_a\n ud_a, ud_b = ud_b, ud_a\n\n if ud_a[0] == \"ball\" and ud_b[0] == \"pocket\":\n self._to_be_destroyed.append(body_a)\n\n def pre_step(self, dt):\n for b in self._to_be_destroyed:\n self.balls.remove(b)\n self.world.destroy_body(b)\n self._to_be_destroyed = []\n\n def ball_at_position(self, pos):\n body = self.world.find_body(pos)\n if body is not None:\n user_data = body.user_data\n if user_data is not None and user_data[0] == \"ball\":\n return body\n return None\n\n def on_mouse_down(self, pos):\n body = self.ball_at_position(pos)\n if body is not None:\n self._selected_ball = body\n self._selected_ball_pos = pos\n return True\n\n return False\n\n def on_mouse_move(self, pos):\n if self._selected_ball is not None:\n self._last_pos = pos\n return True\n return False\n\n def on_mouse_up(self, pos):\n if self._selected_ball is not None:\n self._last_pos = pos\n # if the mouse is in the starting ball itself we do nothing\n if self.ball_at_position(pos) != self._selected_ball:\n delta = b2d.vec2(self._selected_ball_pos) - b2d.vec2(self._last_pos)\n delta *= 100.0\n self._selected_ball.apply_linear_impulse(\n delta, self._selected_ball_pos, True\n )\n self._selected_ball = None\n self._selected_ball_pos = None\n self._last_pos = None\n return False\n\n def post_debug_draw(self):\n\n for pocket in self.pockets:\n self.debug_draw.draw_solid_circle(\n pocket.position, self.ball_radius, (1, 0), (1, 1, 1)\n )\n\n for ball in self.balls:\n _, (color0, color1) = ball.user_data\n\n self.debug_draw.draw_solid_circle(\n ball.position, self.ball_radius, (1, 0), color0\n )\n self.debug_draw.draw_solid_circle(\n ball.position, self.ball_radius / 2, (1, 0), color1\n )\n self.debug_draw.draw_circle(\n ball.position, self.ball_radius, (1, 1, 1), line_width=0.1\n )\n\n if self._selected_ball is not None:\n\n # draw circle around selected ball\n self.debug_draw.draw_circle(\n self._selected_ball.position,\n self.ball_radius * 2,\n (1, 1, 1),\n line_width=0.2,\n )\n\n # mark position on selected ball with red dot\n self.debug_draw.draw_solid_circle(\n self._selected_ball_pos, self.ball_radius * 0.2, (1, 0), (1, 0, 0)\n )\n\n # draw the line between marked pos on ball and last pos\n if self._last_pos is not None:\n self.debug_draw.draw_segment(\n self._selected_ball_pos, self._last_pos, (1, 1, 1), line_width=0.2\n )\ns = JupyterAsyncGui.Settings()\ns.resolution = [1000,1000]\ns.scale = 8\ns.fps = 40\n\ntb = b2d.testbed.run(Billiard, backend=JupyterAsyncGui, gui_settings=s)\n\nasyncio.ensure_future(tb._loop())",
0 commit comments