UI

This nb designs a UI for the blackjack game
from itepifanio_deck.utils import Hand
from itepifanio_deck.core import FrenchDeck

import ipywidgets as widgets
from fastcore.basics import patch

You may need to execute jupyter nbextension enable --py widgetsnbextension and restart the nb to output the widgets.

# !jupyter nbextension enable --py widgetsnbextension

Deck and cards UI

The card and deck are represented as buttons in the UI

def card_button_factory(name: str):
    return widgets.Button(
        disable=True,
        description=name,
        layout=widgets.Layout(height="auto", width="auto"),
    )
card_button = card_button_factory("test")
card_button

Since the deck is a single button we can define them in a Layout

class GameLayout(widgets.TwoByTwoLayout):
    def __init__(self, *args, **kwargs):
        self.deck_button = widgets.Button(
            description="Deck (52)",
            tooltip="click me to draw a card!",
            layout=widgets.Layout(height="100%", width="auto"),
        )

        self.reset_button = widgets.Button(
            description="Reset",
            layout=widgets.Layout(height="auto", width="auto")
        )

        self.displayer = widgets.Text(
            value="Pull a card",
            disabled=True,
            layout=widgets.Layout(width="50%")
        )

        super().__init__(
            top_left=self.deck_button,
            top_right=widgets.HBox([self.reset_button, self.displayer]),
            bottom_right=widgets.HBox([]),
            justify_items="center",
            width="50%",
            align_items="center",
        )
GameLayout()
@patch
def on_deck_click(self: GameLayout, callback):
    self.top_left.on_click(callback)
@patch
def on_reset_click(self: GameLayout, callback):
    reset_button = self.top_right.children[0]
    reset_button.on_click(callback)
@patch
def update_num_cards(self: GameLayout, number: int):
    self.top_left.description = f"Deck ({number})"
@patch
def clear_hand(self: GameLayout):
    self.bottom_right = widgets.HBox([])
@patch
def update_hand(self: GameLayout, hand: Hand):
    suits = {
        'diamonds': '♢',
        'hearts': '♡',
        'spades': '♤',
        'clubs': '♧'
    }

    self.bottom_right = widgets.HBox(
        [card_button_factory(f'{card.rank} {suits[card.suit]}')
         for card in hand]
    )
    self.update_num_cards(52 - len(hand))

    value = hand.value()  # type: ignore

    if value > 21:
        self.displayer.value = f"Game over ({value})"
    elif value == 21:
        self.displayer.value = "You won!"
    else:
        self.displayer.value = str(value)
class BlackJack:
    def __init__(self):
        self.view = GameLayout()
        self.view.on_deck_click(self.on_deck_clicked)
        self.view.on_reset_click(self.on_reset_clicked)
        self._start()

    def _start(self):
        self.deck = FrenchDeck()
        self.deck.shuffle()
        self.hand = Hand()

    def on_deck_clicked(self, event):
        card = self.deck.draw()
        self.hand.draw(card)
        self.view.update_hand(self.hand)

    def on_reset_clicked(self, event):
        self._start()
        self.view.update_hand(self.hand)

    def __repr__(self):
        display(self.view)
        return ""
blackjack = BlackJack()
blackjack