コンテンツにスキップ

Python

Blackjack Game

Card

Bases: element

クラス | カード

Attributes:

Name Type Description
num int

0〜51の識別番号

rank Rank

カードの数字

suit Suit

カードのスーツ

Source code in src/nicegui_blackjack/blackjack.py
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
class Card(ui.element):
    """**クラス** | カード

    :ivar num: 0〜51の識別番号
    :ivar rank: カードの数字
    :ivar suit: カードのスーツ
    """

    num: int
    rank: Rank
    suit: Suit

    def __init__(self, num: int, *, opened: bool = False) -> None:
        """表と裏のdivタグを作成(デフォルトは裏を表示)"""
        super().__init__()
        self.num = num
        self.suit = Suit(num // 13)
        self.rank = cast("Rank", num % 13 + 1)
        char = chr(CARD_CODE + self.suit * 16 + self.rank + (self.rank > 11))  # noqa: PLR2004
        color = "black" if self.suit in {Suit.Spade, Suit.Club} else "red-10"
        # GUI作成
        with self.classes(f"card{' opened' * opened}"):
            ui.label(chr(CARD_CODE)).classes("face front text-blue-10")
            ui.label(char).classes(f"face back text-{color}")

    def open(self) -> None:
        """カードを表にする"""
        self.classes("opened")

    @property
    def opened(self) -> bool:
        """表かどうか"""
        return "opened" in self.classes

    def point(self) -> int:
        """カードの得点"""
        return min(10, self.rank) if self.opened else 0

    def __str__(self) -> str:
        """文字列化"""
        r = " A 2 3 4 5 6 7 8 910 J Q K"[self.rank * 2 - 2 : self.rank * 2]
        return r + f"({'SHDC'[self.suit]})"

opened property

表かどうか

__init__(num, *, opened=False)

表と裏のdivタグを作成(デフォルトは裏を表示)

Source code in src/nicegui_blackjack/blackjack.py
48
49
50
51
52
53
54
55
56
57
58
59
def __init__(self, num: int, *, opened: bool = False) -> None:
    """表と裏のdivタグを作成(デフォルトは裏を表示)"""
    super().__init__()
    self.num = num
    self.suit = Suit(num // 13)
    self.rank = cast("Rank", num % 13 + 1)
    char = chr(CARD_CODE + self.suit * 16 + self.rank + (self.rank > 11))  # noqa: PLR2004
    color = "black" if self.suit in {Suit.Spade, Suit.Club} else "red-10"
    # GUI作成
    with self.classes(f"card{' opened' * opened}"):
        ui.label(chr(CARD_CODE)).classes("face front text-blue-10")
        ui.label(char).classes(f"face back text-{color}")

__str__()

文字列化

Source code in src/nicegui_blackjack/blackjack.py
74
75
76
77
def __str__(self) -> str:
    """文字列化"""
    r = " A 2 3 4 5 6 7 8 910 J Q K"[self.rank * 2 - 2 : self.rank * 2]
    return r + f"({'SHDC'[self.suit]})"

open()

カードを表にする

Source code in src/nicegui_blackjack/blackjack.py
61
62
63
def open(self) -> None:
    """カードを表にする"""
    self.classes("opened")

point()

カードの得点

Source code in src/nicegui_blackjack/blackjack.py
70
71
72
def point(self) -> int:
    """カードの得点"""
    return min(10, self.rank) if self.opened else 0

Dealer

Bases: Owner

クラス | ディーラー

Attributes:

Name Type Description
LOWER Final[int]

この数以上なら山札から引かない

Source code in src/nicegui_blackjack/blackjack.py
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
class Dealer(Owner):
    """**クラス** | ディーラー

    :cvar LOWER: この数以上なら山札から引かない
    """

    LOWER: Final[int] = 17

    async def act(self, game: "Game") -> None:
        """ディーラーの手番の処理"""
        game.set_props(ask_visible=False, message="Dealer's turn")
        logger.debug("Dealer.act: Point %s", self.point())
        while self.point() < self.LOWER:
            if self.cards[1].opened:  # 2枚目がopenedなら3枚目以降を追加
                self.add_card(game.pop())
                await game.sleep(is_bit=True)
            self.cards[-1].open()
            await game.sleep()
            logger.debug("Dealer.act: Opened %s, Point %s", self.cards[-1], self.point())

act(game) async

ディーラーの手番の処理

Source code in src/nicegui_blackjack/blackjack.py
132
133
134
135
136
137
138
139
140
141
142
async def act(self, game: "Game") -> None:
    """ディーラーの手番の処理"""
    game.set_props(ask_visible=False, message="Dealer's turn")
    logger.debug("Dealer.act: Point %s", self.point())
    while self.point() < self.LOWER:
        if self.cards[1].opened:  # 2枚目がopenedなら3枚目以降を追加
            self.add_card(game.pop())
            await game.sleep(is_bit=True)
        self.cards[-1].open()
        await game.sleep()
        logger.debug("Dealer.act: Opened %s, Point %s", self.cards[-1], self.point())

Game

Bases: element

クラス | ゲーム

Attributes:

Name Type Description
nums list[int]

山札(カードの数字のリスト)

dealer Dealer

ディーラー

player Player

プレイヤー

ask_visible bool

カードを引くボタンを表示するかどうか

message str

メッセージ

wait float

カードをめくる時間(秒)

Source code in src/nicegui_blackjack/blackjack.py
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
class Game(ui.element):
    """**クラス** | ゲーム

    :ivar nums: 山札(カードの数字のリスト)
    :ivar dealer: ディーラー
    :ivar player: プレイヤー
    :ivar ask_visible: カードを引くボタンを表示するかどうか
    :ivar message: メッセージ
    :ivar wait: カードをめくる時間(秒)
    """

    nums: list[int]
    dealer: Dealer
    player: Player
    ask_visible: bool
    message: str
    wait: float

    def __init__(self, *, wait: float = 0.6) -> None:
        """CSSの設定"""
        super().__init__()
        self.wait = wait
        ui.add_css(f"""
            .card {{
                width: 68px;
                height: 112px;
                perspective: 1000px;
            }}
            .face {{
                position: absolute;
                width: 100%;
                height: 100%;
                display: flex;
                justify-content: center;
                align-items: center;
                font-size: 8em;
                backface-visibility: hidden;
                transition: transform {self.wait}s;
            }}
            .back {{
                transform: rotateY(180deg);
            }}
            .card.opened .front {{
                transform: rotateY(180deg);
            }}
            .card.opened .back {{
                transform: rotateY(0);
            }}
            .no-select {{
                user-select: none;
            }}
        """)

    def start(self, seed: int | str | None = None, *, nums: list[int] | None = None) -> None:
        """新規ゲーム

        :param seed: 乱数シード, defaults to None
        :param nums: 配布カードのリスト, defaults to None
        """
        if nums is not None:
            self.nums = nums
        else:
            self.nums = [*range(52)]
            if isinstance(seed, str) and seed.isdigit():
                seed = int(seed)
            seed = secrets.randbelow(1000_000_000) if seed is None else seed
            random.seed(seed)
            logger.debug("Game.start: seed %s", seed)
            random.shuffle(self.nums)
        self.set_props(ask_visible=True)
        # GUI作成
        self.clear()
        with self, ui.card().classes("no-select"):
            ui.label("Blackjack Game").classes("text-3xl")
            with ui.column():
                self.dealer = Dealer((self.pop(), self.pop()), opened_num=1, name="Dealer")
                self.player = Player((self.pop(), self.pop()), opened_num=2, name="Player")
                with ui.row():
                    ui.label().bind_text(self, "message").classes("text-2xl font-bold")
                    with ui.row().bind_visibility_from(self, "ask_visible"):
                        ui.button("Hit", on_click=self.hit)
                        ui.button("Stand", on_click=self.stand)
                with ui.row():
                    self._seed = ""
                    ui.button("New Game", on_click=lambda: self.start(self._seed or None)).classes("mt-4")
                    ui.input(label="Seed").bind_value(self, "_seed")

    def set_props(self, *, ask_visible: bool, message: str = "Click your card.") -> None:
        """プレイヤーにカードを引くか尋ねるように設定"""
        self.ask_visible = ask_visible
        self.message = "Draw card?" if ask_visible else message

    def pop(self) -> int:
        """山札(数字)から一枚取る"""
        return self.nums.pop()

    async def hit(self) -> None:
        """Hit処理"""
        await self.player.act(self)

    async def stand(self) -> None:
        """Stand処理"""
        message = "You loss."
        player_point = self.player.point()
        if player_point <= POINT21:
            await self.dealer.act(self)
            dealer_point = self.dealer.point()
            if player_point == dealer_point:
                message = "Draw."
            elif dealer_point > POINT21 or dealer_point < player_point:
                message = "You win."
        self.set_props(ask_visible=False, message=message)

    async def sleep(self, *, is_bit: bool = False) -> None:
        """カードがめくれる間だけ待つ"""
        await asyncio.sleep(self.wait * (1 - 0.7 * is_bit))

__init__(*, wait=0.6)

CSSの設定

Source code in src/nicegui_blackjack/blackjack.py
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
def __init__(self, *, wait: float = 0.6) -> None:
    """CSSの設定"""
    super().__init__()
    self.wait = wait
    ui.add_css(f"""
        .card {{
            width: 68px;
            height: 112px;
            perspective: 1000px;
        }}
        .face {{
            position: absolute;
            width: 100%;
            height: 100%;
            display: flex;
            justify-content: center;
            align-items: center;
            font-size: 8em;
            backface-visibility: hidden;
            transition: transform {self.wait}s;
        }}
        .back {{
            transform: rotateY(180deg);
        }}
        .card.opened .front {{
            transform: rotateY(180deg);
        }}
        .card.opened .back {{
            transform: rotateY(0);
        }}
        .no-select {{
            user-select: none;
        }}
    """)

hit() async

Hit処理

Source code in src/nicegui_blackjack/blackjack.py
257
258
259
async def hit(self) -> None:
    """Hit処理"""
    await self.player.act(self)

pop()

山札(数字)から一枚取る

Source code in src/nicegui_blackjack/blackjack.py
253
254
255
def pop(self) -> int:
    """山札(数字)から一枚取る"""
    return self.nums.pop()

set_props(*, ask_visible, message='Click your card.')

プレイヤーにカードを引くか尋ねるように設定

Source code in src/nicegui_blackjack/blackjack.py
248
249
250
251
def set_props(self, *, ask_visible: bool, message: str = "Click your card.") -> None:
    """プレイヤーにカードを引くか尋ねるように設定"""
    self.ask_visible = ask_visible
    self.message = "Draw card?" if ask_visible else message

sleep(*, is_bit=False) async

カードがめくれる間だけ待つ

Source code in src/nicegui_blackjack/blackjack.py
274
275
276
async def sleep(self, *, is_bit: bool = False) -> None:
    """カードがめくれる間だけ待つ"""
    await asyncio.sleep(self.wait * (1 - 0.7 * is_bit))

stand() async

Stand処理

Source code in src/nicegui_blackjack/blackjack.py
261
262
263
264
265
266
267
268
269
270
271
272
async def stand(self) -> None:
    """Stand処理"""
    message = "You loss."
    player_point = self.player.point()
    if player_point <= POINT21:
        await self.dealer.act(self)
        dealer_point = self.dealer.point()
        if player_point == dealer_point:
            message = "Draw."
        elif dealer_point > POINT21 or dealer_point < player_point:
            message = "You win."
    self.set_props(ask_visible=False, message=message)

start(seed=None, *, nums=None)

新規ゲーム

Parameters:

Name Type Description Default
seed int | str | None

乱数シード, defaults to None

None
nums list[int] | None

配布カードのリスト, defaults to None

None
Source code in src/nicegui_blackjack/blackjack.py
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
def start(self, seed: int | str | None = None, *, nums: list[int] | None = None) -> None:
    """新規ゲーム

    :param seed: 乱数シード, defaults to None
    :param nums: 配布カードのリスト, defaults to None
    """
    if nums is not None:
        self.nums = nums
    else:
        self.nums = [*range(52)]
        if isinstance(seed, str) and seed.isdigit():
            seed = int(seed)
        seed = secrets.randbelow(1000_000_000) if seed is None else seed
        random.seed(seed)
        logger.debug("Game.start: seed %s", seed)
        random.shuffle(self.nums)
    self.set_props(ask_visible=True)
    # GUI作成
    self.clear()
    with self, ui.card().classes("no-select"):
        ui.label("Blackjack Game").classes("text-3xl")
        with ui.column():
            self.dealer = Dealer((self.pop(), self.pop()), opened_num=1, name="Dealer")
            self.player = Player((self.pop(), self.pop()), opened_num=2, name="Player")
            with ui.row():
                ui.label().bind_text(self, "message").classes("text-2xl font-bold")
                with ui.row().bind_visibility_from(self, "ask_visible"):
                    ui.button("Hit", on_click=self.hit)
                    ui.button("Stand", on_click=self.stand)
            with ui.row():
                self._seed = ""
                ui.button("New Game", on_click=lambda: self.start(self._seed or None)).classes("mt-4")
                ui.input(label="Seed").bind_value(self, "_seed")

Owner

Bases: element

クラス | 手札を持ち、カードを引ける人

Attributes:

Name Type Description
cards list[Card]

手札(カードのリスト)

container element

カード追加時のUIコンテナ

Source code in src/nicegui_blackjack/blackjack.py
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
class Owner(ui.element):
    """**クラス** | 手札を持ち、カードを引ける人

    :ivar cards: 手札(カードのリスト)
    :ivar container: カード追加時のUIコンテナ
    """

    cards: list[Card]
    container: ui.element

    def __init__(self, nums: Iterable[int], *, opened_num: int, name: str) -> None:
        """GUIと手札の作成"""
        super().__init__()
        self.container = ui.row()
        with self.container:
            with ui.column().classes("mt-6"):
                ui.label(f"{name}'s cards").classes("text-2xl")
                ui.label().bind_text_from(self, "message").classes("text-2xl pl-6")
            self.cards = [Card(num, opened=i < opened_num) for i, num in enumerate(nums)]

    def add_card(self, num: int, *, opened: bool = False) -> None:
        """手札に一枚加える"""
        with self.container:
            self.cards.append(Card(num, opened=opened))

    def point(self) -> int:
        """手札の合計得点"""
        cards = [card for card in self.cards if card.opened]
        point_ = sum(cd.point() for cd in cards)
        for cd in cards:
            if cd.rank == 1 and point_ + 10 <= POINT21:
                point_ += 10
        return point_

    @property
    def message(self) -> str:
        """メッセージ"""
        return f"point: {self.point()}"

    def __str__(self) -> str:
        """文字列化"""
        return " ".join(f"{card}" if card.opened else f"({card})" for card in self.cards)

message property

メッセージ

__init__(nums, *, opened_num, name)

GUIと手札の作成

Source code in src/nicegui_blackjack/blackjack.py
90
91
92
93
94
95
96
97
98
def __init__(self, nums: Iterable[int], *, opened_num: int, name: str) -> None:
    """GUIと手札の作成"""
    super().__init__()
    self.container = ui.row()
    with self.container:
        with ui.column().classes("mt-6"):
            ui.label(f"{name}'s cards").classes("text-2xl")
            ui.label().bind_text_from(self, "message").classes("text-2xl pl-6")
        self.cards = [Card(num, opened=i < opened_num) for i, num in enumerate(nums)]

__str__()

文字列化

Source code in src/nicegui_blackjack/blackjack.py
119
120
121
def __str__(self) -> str:
    """文字列化"""
    return " ".join(f"{card}" if card.opened else f"({card})" for card in self.cards)

add_card(num, *, opened=False)

手札に一枚加える

Source code in src/nicegui_blackjack/blackjack.py
100
101
102
103
def add_card(self, num: int, *, opened: bool = False) -> None:
    """手札に一枚加える"""
    with self.container:
        self.cards.append(Card(num, opened=opened))

point()

手札の合計得点

Source code in src/nicegui_blackjack/blackjack.py
105
106
107
108
109
110
111
112
def point(self) -> int:
    """手札の合計得点"""
    cards = [card for card in self.cards if card.opened]
    point_ = sum(cd.point() for cd in cards)
    for cd in cards:
        if cd.rank == 1 and point_ + 10 <= POINT21:
            point_ += 10
    return point_

Player

Bases: Owner

クラス | プレイヤー

Source code in src/nicegui_blackjack/blackjack.py
145
146
147
148
149
150
151
152
153
154
155
156
157
158
class Player(Owner):
    """**クラス** | プレイヤー"""

    async def act(self, game: "Game") -> None:
        """プレイヤーの処理"""
        game.set_props(ask_visible=False, message="Player' turn")
        self.add_card(game.pop())
        await game.sleep(is_bit=True)
        self.cards[-1].open()  # 最後のカードを表にする
        await game.sleep()
        if self.point() < POINT21:
            game.set_props(ask_visible=True)
        else:
            await game.stand()  # Stand処理

act(game) async

プレイヤーの処理

Source code in src/nicegui_blackjack/blackjack.py
148
149
150
151
152
153
154
155
156
157
158
async def act(self, game: "Game") -> None:
    """プレイヤーの処理"""
    game.set_props(ask_visible=False, message="Player' turn")
    self.add_card(game.pop())
    await game.sleep(is_bit=True)
    self.cards[-1].open()  # 最後のカードを表にする
    await game.sleep()
    if self.point() < POINT21:
        game.set_props(ask_visible=True)
    else:
        await game.stand()  # Stand処理

Suit

Bases: IntEnum

クラス | カードのスーツ

Source code in src/nicegui_blackjack/blackjack.py
19
20
21
22
23
24
25
26
27
28
29
class Suit(IntEnum):
    """**クラス** | カードのスーツ"""

    Spade = 0
    """スペード"""
    Heart = 1
    """ハート"""
    Diamond = 2
    """ダイヤ"""
    Club = 3
    """クラブ"""

Club = 3 class-attribute instance-attribute

クラブ

Diamond = 2 class-attribute instance-attribute

ダイヤ

Heart = 1 class-attribute instance-attribute

ハート

Spade = 0 class-attribute instance-attribute

スペード

main(*, reload=False, port=8105)

ゲーム実行

Source code in src/nicegui_blackjack/blackjack.py
279
280
281
282
283
def main(*, reload: bool = False, port: int = 8105) -> None:
    """ゲーム実行"""
    basicConfig(level=DEBUG, format="%(message)s")
    Game().start()
    ui.run(title="Blackjack", reload=reload, port=port)