Skip to content

gui_c4

GUI module for the BitBully Connect-4 interactive widget.

Classes:

Name Description
GuiC4

A class which allows to create an interactive Connect-4 widget.

GuiC4

GuiC4()

A class which allows to create an interactive Connect-4 widget.

GuiC4 is an interactive Connect-4 graphical user interface (GUI) implemented using Matplotlib, IPython widgets, and a backend agent from the BitBully engine. It provides the following main features:

  • Interactive Game Board: Presents a dynamic 6-row by 7-column Connect-4 board with clickable board cells.
  • Matplotlib Integration: Utilizes Matplotlib figures to render high-quality game visuals directly within Jupyter notebook environments.
  • User Interaction: Captures and processes mouse clicks and button events, enabling intuitive gameplay via either direct board interaction or button controls.
  • Undo/Redo Moves: Supports undo and redo functionalities, allowing users to navigate through their move history during gameplay.
  • Automated Agent Moves: Incorporates BitBully, a Connect-4 backend engine, enabling computer-generated moves and board evaluations.
  • Game State Handling: Detects game-over scenarios, including win/draw conditions, and provides immediate user feedback through popup alerts.

Attributes:

Name Type Description
notify_output Output

Output widget for notifications and popups.

Examples:

Generally, you should this method to retreive and display the widget.

>>> %matplotlib ipympl
>>> c4gui = GuiC4()
>>> display(c4gui.get_widget())

Methods:

Name Description
destroy

Destroy and release the acquired resources.

get_widget

Get the widget.

Source code in src/bitbully/gui_c4.py
def __init__(self) -> None:
    """Init the GuiC4 widget."""
    # Create a logger with the class name
    self.m_logger = logging.getLogger(self.__class__.__name__)
    self.m_logger.setLevel(logging.DEBUG)  # Set the logging level

    # Create a console handler (optional)
    ch = logging.StreamHandler()
    ch.setLevel(logging.INFO)  # Set level for the handler

    # Create a formatter and add it to the handler
    formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
    ch.setFormatter(formatter)

    # Add the handler to the logger
    self.m_logger.addHandler(ch)

    # Avoid adding handlers multiple times
    self.m_logger.propagate = False
    assets_pth = Path(str(importlib.resources.files("bitbully").joinpath("assets")))
    png_empty = plt.imread(assets_pth.joinpath("empty.png"), format=None)
    png_empty_m = plt.imread(assets_pth.joinpath("empty_m.png"), format=None)
    png_empty_r = plt.imread(assets_pth.joinpath("empty_r.png"), format=None)
    png_red = plt.imread(assets_pth.joinpath("red.png"), format=None)
    png_red_m = plt.imread(assets_pth.joinpath("red_m.png"), format=None)
    png_yellow = plt.imread(assets_pth.joinpath("yellow.png"), format=None)
    png_yellow_m = plt.imread(assets_pth.joinpath("yellow_m.png"), format=None)
    self.m_png = {
        0: {"plain": png_empty, "corner": png_empty_m, "underline": png_empty_r},
        1: {"plain": png_yellow, "corner": png_yellow_m},
        2: {"plain": png_red, "corner": png_red_m},
    }

    self.m_n_row, self.m_n_col = 6, 7

    # TODO: probably not needed:
    self.m_height = np.zeros(7, dtype=np.int32)

    self.m_board_size = 3.5
    # self.m_player = 1
    self.is_busy = False

    self.last_event_time = time.time()

    # Create board first
    self._create_board()

    # Generate buttons for inserting the tokens:
    self._create_buttons()

    # Create control buttons
    self._create_control_buttons()

    # Capture clicks on the field
    _ = self.m_fig.canvas.mpl_connect("button_press_event", self._on_field_click)

    # Movelist
    self.m_movelist: list[tuple[int, int, int]] = []

    # Redo list
    self.m_redolist: list[tuple[int, int, int]] = []

    # Gameover flag:
    self.m_gameover = False

    # C4 agent
    import bitbully_databases as bbd

    # TODO: allow choosing opening book
    db_path: str = bbd.BitBullyDatabases.get_database_path("12-ply-dist")
    self.bitbully_agent = bitbully_core.BitBullyCore(Path(db_path))

bitbully_agent instance-attribute

bitbully_agent = BitBullyCore(Path(db_path))

is_busy instance-attribute

is_busy = False

last_event_time instance-attribute

last_event_time = time()

m_board_size instance-attribute

m_board_size = 3.5

m_gameover instance-attribute

m_gameover = False

m_height instance-attribute

m_height = zeros(7, dtype=int32)

m_logger instance-attribute

m_logger = getLogger(__name__)

m_movelist instance-attribute

m_movelist: list[tuple[int, int, int]] = []

m_png instance-attribute

m_png = {0: {'plain': png_empty, 'corner': png_empty_m, 'underline': png_empty_r}, 1: {'plain': png_yellow, 'corner': png_yellow_m}, 2: {'plain': png_red, 'corner': png_red_m}}

m_redolist instance-attribute

m_redolist: list[tuple[int, int, int]] = []

notify_output class-attribute instance-attribute

notify_output: Output = Output()

destroy

destroy() -> None

Destroy and release the acquired resources.

Source code in src/bitbully/gui_c4.py
def destroy(self) -> None:
    """Destroy and release the acquired resources."""
    plt.close(self.m_fig)
    del self.bitbully_agent
    del self.m_axs
    del self.m_fig
    del self.output

get_widget

get_widget() -> AppLayout

Get the widget.

Examples:

Generally, you should this method to retreive and display the widget.

>>> %matplotlib ipympl
>>> c4gui = GuiC4()
>>> display(c4gui.get_widget())

Returns:

Name Type Description
AppLayout AppLayout

the widget.

Source code in src/bitbully/gui_c4.py
def get_widget(self) -> AppLayout:
    """Get the widget.

    Examples:
        Generally, you should this method to retreive and display the widget.

        ```pycon
        >>> %matplotlib ipympl
        >>> c4gui = GuiC4()
        >>> display(c4gui.get_widget())
        ```

    Returns:
        AppLayout: the widget.
    """
    # Arrange buttons in a row
    insert_button_row = HBox(
        self.m_insert_buttons,
        layout=Layout(
            display="flex",
            flex_flow="row wrap",  # or "column" depending on your layout needs
            justify_content="center",  # Left alignment
            align_items="center",  # Top alignment
        ),
    )
    control_buttons_col = HBox(
        [VBox(list(self.m_control_buttons.values()))],
        layout=Layout(
            display="flex",
            flex_flow="row wrap",  # or "column" depending on your layout needs
            justify_content="flex-end",  # Left alignment
            align_items="center",  # Top alignment
        ),
    )

    tb = self._create_column_labels()

    return AppLayout(
        header=None,
        left_sidebar=control_buttons_col,
        center=VBox(
            [insert_button_row, self.output, tb],
            layout=Layout(
                display="flex",
                flex_flow="column wrap",
                justify_content="flex-start",  # Left alignment
                align_items="flex-start",  # Top alignment
            ),
        ),
        footer=None,
        right_sidebar=None,
    )