Hello World¶
The Interface can be accessed not just from the command line, but also programmatically. In this section, you’ll create a script that creates a billiards system, simulates it, and then visualizes it with the interface.
Consider this hello world for pooltool.
Script¶
For those that want to jump straight in, here is the script:
#! /usr/bin/env python
import pooltool as pt
# We need a table, some balls, and a cue stick
table = pt.Table.default()
balls = pt.get_rack(pt.GameType.NINEBALL, table)
cue = pt.Cue(cue_ball_id="cue")
# Wrap it up as a System
shot = pt.System(table=table, balls=balls, cue=cue)
# Aim at the head ball with a strong impact
shot.cue.set_state(V0=8, phi=pt.aim.at_ball(shot, "1"))
# Evolve the shot.
pt.simulate(shot, inplace=True)
# Open up the shot in the GUI
pt.show(shot)
The rest of this document details a line-by-line explanation.
Explanation¶
Importing¶
First thing’s first, the pooltool package is imported which gives you access to the top-level API:
import pooltool as pt
The most important classes and functions are available directly from this top-level API.
Creating a System¶
Then a table, a cue stick, and a collection of balls are created and wrapped up into a System:
# We need a table, some balls, and a cue stick
table = pt.Table.default()
balls = pt.get_rack(pt.GameType.NINEBALL, table)
cue = pt.Cue(cue_ball_id="cue")
# Wrap it up as a System
shot = pt.System(table=table, balls=balls, cue=cue)
The System is a fundamental object of the pooltool API
- class System(cue: Cue, table: Table, balls: Any, t: float = 0.0, events: list[Event] = list)[source]
A class representing the billiards system.
This class holds:
a collection of balls (
pooltool.objects.Ball)a cue stick (
pooltool.objects.Cue)a table (
pooltool.objects.Table)
Together, these objects, referred to as the system, fully describe the billiards system.
This object is a mutable object that can be evolved over the course of system’s evolution. When a billiards system is simulated, a list of
pooltool.events.Eventobjects is stored in this class.This class also stores the duration of simulated time elapsed as
t, measured in seconds.Attributes:
- cue : Cue
A cue stick.
- table : Table
A table.
- balls : dict[str, Ball]
A dictionary of balls.
Warning
Each key must match each value’s
id(e.g.{"2": Ball(id="1")}is invalid).Note
If, during construction, a sequence (e.g. list, tuple, etc.) of balls is passed instead of a dictionary, it will be converted to a dictionary.
- t : float
The elapsed simulation time. If the system is in the process of being simulated,
tis updated to be the number of seconds the system has evolved. After being simulated,tremains at the final simulation time.
- events : list[Event]
The sequence of events in the simulation. Like
t, this is updated incrementally as the system is evolved. (default =[])
Examples
Constructing a system requires a cue, a table, and a dictionary of balls:
>>> import pooltool as pt >>> pt.System( >>> cue=pt.Cue.default(), >>> table=pt.Table.default(), >>> balls={"1": pt.Ball.create("1", xy=(0.2, 0.3))}, >>> )
If you need a quick system to experiment with, call
example():>>> import pooltool as pt >>> system = pt.System.example()
You can simulate this system and introspect its attributes:
>>> pt.simulate(system, inplace=True) >>> system.simulated True >>> len(system.events) 14 >>> system.cue <Cue object at 0x7fb838080190> ├── V0 : 1.5 ├── phi : 95.07668213305062 ├── a : 0.0 ├── b : -0.3 └── theta : 0.0
This
systemcan also be visualized in the GUI:>>> pt.show(system)
Aiming the cue stick¶
The cue stick parameters are then set with Cue.set_state. A large impact speed of V0=8 (m/s) is chosen, and an aiming utility function is used to aim the cue ball directly at the one-ball.
# Aim at the head ball with a strong impact
shot.cue.set_state(V0=8, phi=pt.aim.at_ball(shot, "1"))
The Cue holds all aiming information
A description of the cue’s parameters can be found in the API docs:
- class Cue(id: str = 'cue_stick', V0: float = 2.0, phi: float = 0.0, theta: float = 0.0, a: float = 0.0, b: float = 0.25, cue_ball_id: str = 'cue', specs: CueSpecs = CueSpecs.default)[source]
A cue stick.
Attributes:
- id : str
An ID for the cue.
- V0 : float
The impact speed.
Units are m/s.
Note
This is the speed of the cue stick upon impact, not the speed of the ball upon impact.
- phi : float
The directional strike angle.
The horizontal direction of the cue’s orientation relative to the table layout. Specified in degrees.
If you imagine facing from the head rail (where the cue is positioned for a break shot) towards the foot rail (where the balls are racked),
\(\phi = 0\) corresponds to striking the cue ball to the right
\(\phi = 90\) corresponds to striking the cue ball towards the foot rail
\(\phi = 180\) corresponds to striking the cue ball to the left
\(\phi = 270\) corresponds to striking the cue ball towards the head rail
\(\phi = 360\) corresponds to striking the cue ball to the right
- theta : float
The cue inclination angle.
The vertical angle of the cue stick relative to the table surface. Specified in degrees.
\(\theta = 0\) corresponds to striking the cue ball parallel with the table (no massé)
\(\theta = 90\) corresponds to striking the cue ball downwards into the table (max massé)
- a : float
The amount and direction of side spin.
\(a = -1\) is the rightmost side of ball
\(a = +1\) is the leftmost side of the ball
- b : float
The amount of top/bottom spin.
\(b = -1\) is the bottom-most side of the ball
\(b = +1\) is the top-most side of the ball
- cue_ball_id : str
The ball ID of the ball being cued.
- specs : CueSpecs
The cue specs.
Simulating a system¶
The cue parameters have been set, but the system still hasn’t been simulated. This is done with a call to pooltool.evolution.simulate().
# Evolve the shot.
pt.simulate(shot, inplace=True)
How system simulation works
- class simulate(shot: System, engine: PhysicsEngine | None = None, inplace: bool = False, continuous: bool = False, dt: float | None = None, t_final: float | None = None, include: set[EventType] = {EventType.BALL_BALL, EventType.BALL_CIRCULAR_CUSHION, EventType.BALL_LINEAR_CUSHION, EventType.BALL_POCKET, EventType.NONE, EventType.ROLLING_SPINNING, EventType.ROLLING_STATIONARY, EventType.SLIDING_ROLLING, EventType.SPINNING_STATIONARY, EventType.STICK_BALL}, max_events: int = 0)[source]
Run a simulation on a system and return it
- Parameters:
shot : System
The system you would like simulated. The system should already have energy, otherwise there will be nothing to simulate.
engine : PhysicsEngine | None
The engine holds all of the physics. You can instantiate your very own
pooltool.physics.PhysicsEngineobject, or you can modify~/.config/pooltool/physics/resolver.jsonto change the default engine.inplace : bool
By default, a copy of the passed system is simulated and returned. This leaves the passed system unmodified. If inplace is set to True, the passed system is modified in place, meaning no copy is made and the returned system is the passed system. For a more practical distinction, see Examples below.
continuous : bool
If True, the system will not only be simulated, but it will also be “continuized”. This means each ball will be populated with a ball history with small fixed timesteps that make it ready for visualization.
-
The small fixed timestep used when continuous is True.
-
If set, the simulation will end prematurely after the calculation of an event with
event.time > t_final. -
Which EventType are you interested in resolving? By default, all detected events are resolved.
max_events : int
If this is greater than 0, and the shot has more than this many events, the simulation is stopped and the balls are set to stationary.
- Returns:
The simulated system.
- Return type:
Examples
Standard usage:
>>> # Simulate a system >>> import pooltool as pt >>> system = pt.System.example() >>> simulated_system = pt.simulate(system) >>> assert not system.simulated >>> assert simulated_system.simulated
The returned system is simulated, but the passed system remains unchanged.
You can also modify the system in place:
>>> # Simulate a system in place >>> import pooltool as pt >>> system = pt.System.example() >>> simulated_system = pt.simulate(system, inplace=True) >>> assert system.simulated >>> assert simulated_system.simulated >>> assert system is simulated_system
Notice that the returned system _is_ the simulated system. Therefore, there is no point catching the return object when inplace is True:
>>> # Simulate a system in place >>> import pooltool as pt >>> system = pt.System.example() >>> assert not system.simulated >>> pt.simulate(system, inplace=True) >>> assert system.simulated
You can continuize the ball trajectories with continuous
>>> # Simulate a system in place >>> import pooltool as pt >>> system = pt.simulate(pt.System.example(), continuous=True) >>> for ball in system.balls.values(): assert len(ball.history_cts) > 0
See also
The system has now been evolved from its initial to its final state.
Opening the GUI¶
To visualize the shot, open the GUI with pooltool.show():
# Open up the shot in the GUI
pt.show(shot)
The GUI
- class show(*args, **kwargs)[source]
Opens the interactive interface for one or more shots.
Important
For instructions on how to use the interactive interface, see The Interface.
- Parameters:
shot_or_shots
The shot or collection of shots to visualize. This can be a single
pooltool.system.Systemobject or apooltool.system.MultiSystemobject containing multiple systems.Note
If a multisystem is passed, the systems can be scrolled through by pressing n (next) and p (previous). When using
show(), press Enter to toggle parallel visualization mode where all systems play simultaneously with reduced opacity except the active one. In parallel mode, use n and p to change which system has full opacity. Note that parallel visualization is only available inshow()and not when playing the game through the CLIrun-pooltool.title
The title to display in the visualization. Defaults to an empty string.
camera_state
The initial camera state that the visualization is rendered with.
Example
This example visualizes a single shot.
>>> import pooltool as pt >>> system = pt.System.example()
Make sure the shot is simulated, otherwise it will make for a boring visualization:
>>> pt.simulate(system, inplace=True)
Now visualize the shot:
>>> pt.show(system)
(Press escape to exit the interface and continue script execution)
Next¶
Obviously, this script is just the beginning. Pooltool offers much more than this, which means you have much more to learn. From here, you should check out the Examples page and dive into whatever topic interests you.