mirror of
https://github.com/peter-tanner/starcore-explorer-bad.git
synced 2024-12-03 02:20:28 +08:00
300 lines
10 KiB
Plaintext
300 lines
10 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"import sys\n",
|
|
"if \"pyodide\" in sys.modules:\n",
|
|
" import piplite\n",
|
|
" await piplite.install('pyb2d-jupyterlite-backend>=0.4.2')"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"import numpy\n",
|
|
"import b2d\n",
|
|
"import math\n",
|
|
"import random\n",
|
|
"\n",
|
|
"from b2d.testbed import TestbedBase\n",
|
|
"\n",
|
|
"class 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",
|
|
" )"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Controlls\n",
|
|
"* To play this game, click and hold inside a billiard ball, move and release the mouse to shoot the ball.\n",
|
|
"* Use the mouse-wheel to zoom in/out, a\n",
|
|
"* Click and drag in the empty space to translate the view."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from pyb2d_jupyterlite_backend.async_jupyter_gui import JupyterAsyncGui\n",
|
|
"backend = JupyterAsyncGui\n",
|
|
"s = backend.Settings()\n",
|
|
"s.resolution = [500,600]\n",
|
|
"s.scale = 8\n",
|
|
"s.fps = 40\n",
|
|
"s.translate = [125,100]\n",
|
|
"b2d.testbed.run(Billiard, backend=backend, gui_settings=s);"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": []
|
|
}
|
|
],
|
|
"metadata": {
|
|
"kernelspec": {
|
|
"display_name": "Python 3 (ipykernel)",
|
|
"language": "python",
|
|
"name": "python3"
|
|
},
|
|
"language_info": {
|
|
"codemirror_mode": {
|
|
"name": "ipython",
|
|
"version": 3
|
|
},
|
|
"file_extension": ".py",
|
|
"mimetype": "text/x-python",
|
|
"name": "python",
|
|
"nbconvert_exporter": "python",
|
|
"pygments_lexer": "ipython3",
|
|
"version": "3.10.4"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 4
|
|
}
|