21 点(英文:Blackjack)是一种在赌场玩的纸牌游戏。这种游戏的参与者不是互相竞争,而是与赌场指定的庄家竞争。在本文中,我们将从头开始创建可在终端上玩的玩家与庄家之间的二十一点游戏。
二十一点规则
我们将为从未玩过二十一点的读者提供一套简短的规则。21 点的神奇数字是 21。玩家拿到的所有牌的价值相加,如果总和超过 21,玩家就会立即爆牌并输掉。
如果玩家拿到的牌正好是 21,那么玩家就赢了庄家。否则,要想获胜,玩家的牌数总和必须大于庄家的牌数总和。
每张扑克牌的面值都是 10,而 A 可以被算作 1 或 11,这对玩家的获胜机会非常有利。其余牌的价值由其编号决定。
二十一点游戏的发牌过程如下:
- 一张牌发给朝上的玩家(所有人都能看到)。
- 庄家给自己发一张所有人都能看到的牌。
- 另一张牌发给朝上的玩家。
- 庄家给自己发一张朝下的牌。
- 玩家必须决定是用现有的牌站着还是再拿一张牌。
- 如果玩家决定打牌,庄家会再发一张牌。
- 如果玩家决定不出牌,庄家就会亮出隐藏的牌。
- 庄家无权决定玩家是跟还是不跟。一般规则是,如果庄家牌的总和小于 17,庄家需要继续打出更多的牌。
- 一旦庄家牌的总和达到或超过 17 点,庄家就有义务不出牌。
- 根据最后的牌数决定胜负。
了解规则后,21 点游戏的编程就变得简单了。从零开始创建基于终端的游戏需要三个主要部分:游戏设计、游戏逻辑和玩家互动管理。
用 Python 设计二十一点
首先,我们将进行游戏设计。我们的任务是在终端上有效地显示一系列牌,如下图所示。
我们需要一个能打印卡片序列且与卡片数量无关的函数。此外,它还必须提供在需要时打印隐藏卡片的功能。
下面的代码解决了我们的问题。
# 打印卡片的功能
def print_cards(cards, hidden):
s = ""
for card in cards:
s = s + "\t ________________"
if hidden:
s += "\t ________________"
print(s)
s = ""
for card in cards:
s = s + "\t| |"
if hidden:
s += "\t| |"
print(s)
s = ""
for card in cards:
if card.value == '10':
s = s + "\t| {} |".format(card.value)
else:
s = s + "\t| {} |".format(card.value)
if hidden:
s += "\t| |"
print(s)
s = ""
for card in cards:
s = s + "\t| |"
if hidden:
s += "\t| * * |"
print(s)
s = ""
for card in cards:
s = s + "\t| |"
if hidden:
s += "\t| * * |"
print(s)
s = ""
for card in cards:
s = s + "\t| |"
if hidden:
s += "\t| * * |"
print(s)
s = ""
for card in cards:
s = s + "\t| |"
if hidden:
s += "\t| * * |"
print(s)
s = ""
for card in cards:
s = s + "\t| {} |".format(card.suit)
if hidden:
s += "\t| * |"
print(s)
s = ""
for card in cards:
s = s + "\t| |"
if hidden:
s += "\t| * |"
print(s)
s = ""
for card in cards:
s = s + "\t| |"
if hidden:
s += "\t| * |"
print(s)
s = ""
for card in cards:
s = s + "\t| |"
if hidden:
s += "\t| |"
print(s)
s = ""
for card in cards:
s = s + "\t| |"
if hidden:
s += "\t| |"
print(s)
s = ""
for card in cards:
if card.value == '10':
s = s + "\t| {} |".format(card.value)
else:
s = s + "\t| {} |".format(card.value)
if hidden:
s += "\t| * |"
print(s)
s = ""
for card in cards:
s = s + "\t|________________|"
if hidden:
s += "\t|________________|"
print(s)
print()
每张卡片的详细信息都存储为一个卡片对象。print_cards() 函数的第二个参数是一个布尔值,表示是否显示隐藏的卡片。
创建卡片
在类和对象的帮助下,我们可以创建一个花色和数值的组合来表示一张 “扑克牌”。在二十一点游戏中,一张牌有三个属性:花色、代表值和作为分数的值。
所有上述属性都在下面的 "卡 "类中维护。
# 卡片类别的定义
class Card:
def __init__(self, suit, value, card_value):
# 纸牌的花色,如黑桃和梅花
self.suit = suit
# 表示牌的价值,如 A 表示 Ace,K 表示 King
self.value = value
# 卡片的分值,如 10 分代表国王
self.card_value = card_value
使用上述类,我们可以创建一副包含 52 个 Card 对象的纸牌。
一些基本价值观
每种纸牌游戏都需要一些基本价值,如花色类型、纸牌类型以及每张纸牌的价值。
为了更方便理解,我代码和备注都用全英文了
# The type of suit
suits = ["Spades", "Hearts", "Clubs", "Diamonds"]
# The suit value
suits_values = {"Spades":"\u2664", "Hearts":"\u2661", "Clubs": "\u2667", "Diamonds": "\u2662"}
# The type of card
cards = ["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"]
# The card value
cards_values = {"A": 11, "2":2, "3":3, "4":4, "5":5, "6":6, "7":7, "8":8, "9":9, "10":10, "J":10, "Q":10, "K":10}
这里需要注意的一点是,A 最初被标记为 11 点。这一策略背后的理念是,每当玩家/发牌者的分数似乎超过 21 分时,我们就可以将 A 的分数(如果发牌)减为 1 分。
我们稍后将在本文中看到减分的实施。
生成一副扑克牌
一副普通的扑克牌由 52 张牌组成,每张牌都有不同的花色和价值组合。利用上述基本值和卡片类,我们可以生成一副扑克牌。
# The deck of cards
deck = []
# Loop for every type of suit
for suit in suits:
# Loop for every type of card in a suit
for card in cards:
# Adding card to the deck
deck.append(Card(suits_values[suit], card, cards_values[card]))
实际上,21 点游戏涉及多副扑克牌,因此可以重复使用上述循环来填充多副扑克牌。
新创建的牌面将传递给执行游戏的函数。
blackjack_game(deck)
让我们来了解一下玩家和电脑发牌员之间 21 点游戏单次迭代背后的游戏逻辑。
声明重要的游戏变量
在任何时刻,我们都需要以下游戏变量:
- 玩家和庄家的发牌列表。
- 双方牌值的总和。
# Function for a single game of blackjack
def blackjack_game(deck):
global cards_values
# Cards for both dealer and player
player_cards = []
dealer_cards = []
# Scores for both dealer and player
player_score = 0
dealer_score = 0
当我们设计游戏逻辑时,这些游戏变量就会发挥作用。
Python 二十一点游戏逻辑
整个游戏逻辑都围绕着发牌和玩家对击中或站立的选择。只要我们处理好上述两件事,今天的工作就完成了。
发牌的第一阶段:必发牌
初始发牌包括给玩家和庄家两张牌。不过,庄家的第二张牌必须保持未知。
# Initial dealing for player and dealer
while len(player_cards) < 2:
# Randomly dealing a card
player_card = random.choice(deck)
player_cards.append(player_card)
deck.remove(player_card)
# Updating the player score
player_score += player_card.card_value
# In case both the cards are Ace, make the first ace value as 1
if len(player_cards) == 2:
if player_cards[0].card_value == 11 and player_cards[1].card_value == 11:
player_cards[0].card_value = 1
player_score -= 10
# Print player cards and score
print("PLAYER CARDS: ")
print_cards(player_cards, False)
print("PLAYER SCORE = ", player_score)
input()
# Randomly dealing a card
dealer_card = random.choice(deck)
dealer_cards.append(dealer_card)
deck.remove(dealer_card)
# Updating the dealer score
dealer_score += dealer_card.card_value
# Print dealer cards and score, keeping in mind to hide the second card and score
print("DEALER CARDS: ")
if len(dealer_cards) == 1:
print_cards(dealer_cards, False)
print("DEALER SCORE = ", dealer_score)
else:
print_cards(dealer_cards[:-1], True)
print("DEALER SCORE = ", dealer_score - dealer_cards[-1].card_value)
# In case both the cards are Ace, make the second ace value as 1
if len(dealer_cards) == 2:
if dealer_cards[0].card_value == 11 and dealer_cards[1].card_value == 11:
dealer_cards[1].card_value = 1
dealer_score -= 10
input()
# Player gets a blackjack
if player_score == 21:
print("PLAYER HAS A BLACKJACK!!!!")
print("PLAYER WINS!!!!")
quit()
对于一个看似简单的交易来说,这可能是一个很大的挑战。让我们来了解一下上述代码所涉及的过程:
- 主循环一直运行到玩家和庄家各拿到两张牌为止。
- 庄家从牌组中随机抽取一张牌,然后在下一步中将这张牌从牌组中移除。
- 这张牌的价值加到玩家的分数中。
- 同样,庄家也会随机抽取一张牌,并将其价值计入庄家得分。
- 玩家的牌正常显示在屏幕上。
- 庄家的牌会小心显示,不会显示第二张牌及其牌值。
- 如果任何一方拿到双 A,则会调整其分数,以确保双方都不爆牌。
- 上述所有步骤顺利完成后,我们进入第二阶段发牌。
注:玩家和庄家的分数调整有细微差别。前者调整的是第一张牌的价值,而后者调整的是第二张牌的价值。
之所以要调整第二张牌的价值,是因为如果我们调整了第一张牌的价值,就会暴露隐藏牌是 Ace 的身份。
最后要做的一件事是检查玩家是否已经拥有 21 点。如果有,玩家获胜,游戏结束。
注:input() 函数会暂停程序,直到玩家按下 "ENTER "键。这样可以防止所有游戏事件的快速回放。
clear()函数负责清除终端,为游戏提供一个干净的界面。
第二阶段发牌:玩家的选择
发牌的第二阶段取决于玩家的决定,是要另一张牌来提高分数,还是维持现有的牌。
# Print dealer and player cards
print("DEALER CARDS: ")
print_cards(dealer_cards[:-1], True)
print("DEALER SCORE = ", dealer_score - dealer_cards[-1].card_value)
print()
print("PLAYER CARDS: ")
print_cards(player_cards, False)
print("PLAYER SCORE = ", player_score)
# Managing the player moves
while player_score < 21:
choice = input("Enter H to Hit or S to Stand : ")
# Sanity checks for player's choice
if len(choice) != 1 or (choice.upper() != 'H' and choice.upper() != 'S'):
clear()
print("Wrong choice!! Try Again")
# If player decides to HIT
if choice.upper() == 'H':
# Dealing a new card
player_card = random.choice(deck)
player_cards.append(player_card)
deck.remove(player_card)
# Updating player score
player_score += player_card.card_value
# Updating player score in case player's card have ace in them
c = 0
while player_score > 21 and c < len(player_cards):
if player_cards[c].card_value == 11:
player_cards[c].card_value = 1
player_score -= 10
c += 1
else:
c += 1
clear()
# Print player and dealer cards
print("DEALER CARDS: ")
print_cards(dealer_cards[:-1], True)
print("DEALER SCORE = ", dealer_score - dealer_cards[-1].card_value)
print()
print("PLAYER CARDS: ")
print_cards(player_cards, False)
print("PLAYER SCORE = ", player_score)
# If player decides to Stand
if choice.upper() == 'S':
break
# Check if player has a Blackjack
if player_score == 21:
print("PLAYER HAS A BLACKJACK")
quit()
# Check if player busts
if player_score > 21:
print("PLAYER BUSTED!!! GAME OVER!!!")
quit()
玩家决定是打还是不打,直到分数超过 21 分或玩家决定不打为止。玩家发牌的数量没有限制,只有分数限制。
玩家每决定击中一次,就会从牌组中发出一张新牌,分数也会随之更新。如前所述,A 可以算作 1 或 11。如果分数超过 21,一段特殊的代码会将 A 的值从 11 转换为 1。
当玩家对当前分数满意时,他就会起立。当他这样做时,我们会在进行一些强制性检查(如二十一点或爆牌情况)后进入发牌的最后阶段。
发牌的最后阶段:庄家的牌
在发牌的最后阶段,庄家的暗牌会被揭开,庄家的分数也会被揭开。根据二十一点的标准规则,庄家必须给自己发更多的牌,直到分数大于或等于 17。
# Managing the dealer moves
while dealer_score < 17:
clear()
print("DEALER DECIDES TO HIT.....")
# Dealing card for dealer
dealer_card = random.choice(deck)
dealer_cards.append(dealer_card)
deck.remove(dealer_card)
# Updating the dealer's score
dealer_score += dealer_card.card_value
# Updating player score in case player's card have ace in them
c = 0
while dealer_score > 21 and c < len(dealer_cards):
if dealer_cards[c].card_value == 11:
dealer_cards[c].card_value = 1
dealer_score -= 10
c += 1
else:
c += 1
# print player and dealer cards
print("PLAYER CARDS: ")
print_cards(player_cards, False)
print("PLAYER SCORE = ", player_score)
print()
print("DEALER CARDS: ")
print_cards(dealer_cards, False)
print("DEALER SCORE = ", dealer_score)
input()
庄家继续击球,直到分数越过 17 分为止。如果需要,我们也有类似的方法将 A 的牌值从 11 转换为 1。
游戏终结
当庄家的分数是 17 或更多时,我们就进入 “终局”,即比较牌局的胜负。可能会出现以下几种情况:
- 庄家爆牌 - 庄家的分数超过 21。
- 庄家有 21 点 - 庄家的准确分数是 21。
- 平局 - 玩家和庄家的分数相同。
- 闲家赢 - 闲家的分数超过庄家。
- 庄家赢 - 庄家的分数超过闲家。
我们会检查上述每种可能性,并宣布获胜者。
# Dealer busts
if dealer_score > 21:
print("DEALER BUSTED!!! YOU WIN!!!")
quit()
# Dealer gets a blackjack
if dealer_score == 21:
print("DEALER HAS A BLACKJACK!!! PLAYER LOSES")
quit()
# TIE Game
if dealer_score == player_score:
print("TIE GAME!!!!")
# Player Wins
elif player_score > dealer_score:
print("PLAYER WINS!!!")
# Dealer Wins
else:
print("DEALER WINS!!!")
至此,玩家与庄家之间的 21 点游戏的单次迭代结束。
二十一点游戏的完整 Python 代码
import random
import os
import time
# The Card class definition
class Card:
def __init__(self, suit, value, card_value):
# Suit of the Card like Spades and Clubs
self.suit = suit
# Representing Value of the Card like A for Ace, K for King
self.value = value
# Score Value for the Card like 10 for King
self.card_value = card_value
# Clear the terminal
def clear():
os.system("clear")
# Function to print the cards
def print_cards(cards, hidden):
s = ""
for card in cards:
s = s + "\t ________________"
if hidden:
s += "\t ________________"
print(s)
s = ""
for card in cards:
s = s + "\t| |"
if hidden:
s += "\t| |"
print(s)
s = ""
for card in cards:
if card.value == '10':
s = s + "\t| {} |".format(card.value)
else:
s = s + "\t| {} |".format(card.value)
if hidden:
s += "\t| |"
print(s)
s = ""
for card in cards:
s = s + "\t| |"
if hidden:
s += "\t| * * |"
print(s)
s = ""
for card in cards:
s = s + "\t| |"
if hidden:
s += "\t| * * |"
print(s)
s = ""
for card in cards:
s = s + "\t| |"
if hidden:
s += "\t| * * |"
print(s)
s = ""
for card in cards:
s = s + "\t| |"
if hidden:
s += "\t| * * |"
print(s)
s = ""
for card in cards:
s = s + "\t| {} |".format(card.suit)
if hidden:
s += "\t| * |"
print(s)
s = ""
for card in cards:
s = s + "\t| |"
if hidden:
s += "\t| * |"
print(s)
s = ""
for card in cards:
s = s + "\t| |"
if hidden:
s += "\t| * |"
print(s)
s = ""
for card in cards:
s = s + "\t| |"
if hidden:
s += "\t| |"
print(s)
s = ""
for card in cards:
s = s + "\t| |"
if hidden:
s += "\t| |"
print(s)
s = ""
for card in cards:
if card.value == '10':
s = s + "\t| {} |".format(card.value)
else:
s = s + "\t| {} |".format(card.value)
if hidden:
s += "\t| * |"
print(s)
s = ""
for card in cards:
s = s + "\t|________________|"
if hidden:
s += "\t|________________|"
print(s)
print()
# Function for a single game of blackjack
def blackjack_game(deck):
# Cards for both dealer and player
player_cards = []
dealer_cards = []
# Scores for both dealer and player
player_score = 0
dealer_score = 0
clear()
# Initial dealing for player and dealer
while len(player_cards) < 2:
# Randomly dealing a card
player_card = random.choice(deck)
player_cards.append(player_card)
deck.remove(player_card)
# Updating the player score
player_score += player_card.card_value
# In case both the cards are Ace, make the first ace value as 1
if len(player_cards) == 2:
if player_cards[0].card_value == 11 and player_cards[1].card_value == 11:
player_cards[0].card_value = 1
player_score -= 10
# Print player cards and score
print("PLAYER CARDS: ")
print_cards(player_cards, False)
print("PLAYER SCORE = ", player_score)
input()
# Randomly dealing a card
dealer_card = random.choice(deck)
dealer_cards.append(dealer_card)
deck.remove(dealer_card)
# Updating the dealer score
dealer_score += dealer_card.card_value
# Print dealer cards and score, keeping in mind to hide the second card and score
print("DEALER CARDS: ")
if len(dealer_cards) == 1:
print_cards(dealer_cards, False)
print("DEALER SCORE = ", dealer_score)
else:
print_cards(dealer_cards[:-1], True)
print("DEALER SCORE = ", dealer_score - dealer_cards[-1].card_value)
# In case both the cards are Ace, make the second ace value as 1
if len(dealer_cards) == 2:
if dealer_cards[0].card_value == 11 and dealer_cards[1].card_value == 11:
dealer_cards[1].card_value = 1
dealer_score -= 10
input()
# Player gets a blackjack
if player_score == 21:
print("PLAYER HAS A BLACKJACK!!!!")
print("PLAYER WINS!!!!")
quit()
clear()
# Print dealer and player cards
print("DEALER CARDS: ")
print_cards(dealer_cards[:-1], True)
print("DEALER SCORE = ", dealer_score - dealer_cards[-1].card_value)
print()
print("PLAYER CARDS: ")
print_cards(player_cards, False)
print("PLAYER SCORE = ", player_score)
# Managing the player moves
while player_score < 21:
choice = input("Enter H to Hit or S to Stand : ")
# Sanity checks for player's choice
if len(choice) != 1 or (choice.upper() != 'H' and choice.upper() != 'S'):
clear()
print("Wrong choice!! Try Again")
# If player decides to HIT
if choice.upper() == 'H':
# Dealing a new card
player_card = random.choice(deck)
player_cards.append(player_card)
deck.remove(player_card)
# Updating player score
player_score += player_card.card_value
# Updating player score in case player's card have ace in them
c = 0
while player_score > 21 and c < len(player_cards):
if player_cards[c].card_value == 11:
player_cards[c].card_value = 1
player_score -= 10
c += 1
else:
c += 1
clear()
# Print player and dealer cards
print("DEALER CARDS: ")
print_cards(dealer_cards[:-1], True)
print("DEALER SCORE = ", dealer_score - dealer_cards[-1].card_value)
print()
print("PLAYER CARDS: ")
print_cards(player_cards, False)
print("PLAYER SCORE = ", player_score)
# If player decides to Stand
if choice.upper() == 'S':
break
clear()
# Print player and dealer cards
print("PLAYER CARDS: ")
print_cards(player_cards, False)
print("PLAYER SCORE = ", player_score)
print()
print("DEALER IS REVEALING THE CARDS....")
print("DEALER CARDS: ")
print_cards(dealer_cards, False)
print("DEALER SCORE = ", dealer_score)
# Check if player has a Blackjack
if player_score == 21:
print("PLAYER HAS A BLACKJACK")
quit()
# Check if player busts
if player_score > 21:
print("PLAYER BUSTED!!! GAME OVER!!!")
quit()
input()
# Managing the dealer moves
while dealer_score < 17:
clear()
print("DEALER DECIDES TO HIT.....")
# Dealing card for dealer
dealer_card = random.choice(deck)
dealer_cards.append(dealer_card)
deck.remove(dealer_card)
# Updating the dealer's score
dealer_score += dealer_card.card_value
# Updating player score in case player's card have ace in them
c = 0
while dealer_score > 21 and c < len(dealer_cards):
if dealer_cards[c].card_value == 11:
dealer_cards[c].card_value = 1
dealer_score -= 10
c += 1
else:
c += 1
# print player and dealer cards
print("PLAYER CARDS: ")
print_cards(player_cards, False)
print("PLAYER SCORE = ", player_score)
print()
print("DEALER CARDS: ")
print_cards(dealer_cards, False)
print("DEALER SCORE = ", dealer_score)
input()
# Dealer busts
if dealer_score > 21:
print("DEALER BUSTED!!! YOU WIN!!!")
quit()
# Dealer gets a blackjack
if dealer_score == 21:
print("DEALER HAS A BLACKJACK!!! PLAYER LOSES")
quit()
# TIE Game
if dealer_score == player_score:
print("TIE GAME!!!!")
# Player Wins
elif player_score > dealer_score:
print("PLAYER WINS!!!")
# Dealer Wins
else:
print("DEALER WINS!!!")
if __name__ == '__main__':
# The type of suit
suits = ["Spades", "Hearts", "Clubs", "Diamonds"]
# The suit value
suits_values = {"Spades":"\u2664", "Hearts":"\u2661", "Clubs": "\u2667", "Diamonds": "\u2662"}
# The type of card
cards = ["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"]
# The card value
cards_values = {"A": 11, "2":2, "3":3, "4":4, "5":5, "6":6, "7":7, "8":8, "9":9, "10":10, "J":10, "Q":10, "K":10}
# The deck of cards
deck = []
# Loop for every type of suit
for suit in suits:
# Loop for every type of card in a suit
for card in cards:
# Adding card to the deck
deck.append(Card(suits_values[suit], card, cards_values[card]))
blackjack_game(deck)
读者不必遵循整个编码顺序。通过增加多人对庄家的功能,可以对上述代码进行各种修改。
结论
二十一点游戏起初看似简单随意,但只有当玩家遵循某些策略(如算牌)时,游戏才会变得复杂。
世界上流传着许多不同版本的 21 点游戏,如瑞典酒吧 21 点游戏和家庭游戏 21 点游戏。好奇的读者可以了解这些变体,并尝试利用本文中获得的知识来实施它们。
感谢大家花时间阅读我的文章,你们的支持是我不断前进的动力。期望未来能为大家带来更多有价值的内容,请多多关注我的动态!