Python 小型项目大全 1~5

2023-04-12 10:59:14 浏览数 (1)

一、百吉饼

原文:http://inventwithpython.com/bigbookpython/project1.html

在百吉饼这种演绎逻辑游戏中,你必须根据线索猜出一个秘密的三位数。该游戏提供以下提示之一来响应您的猜测:"Pico",当您的猜测在错误的位置有正确的数字时,"Fermi",当您的猜测在正确的位置有正确的数字时,以及"Bagels",如果您的猜测没有正确的数字。你有 10 次机会猜这个秘密数字。

运行示例

当您运行bagels.py时,输出将如下所示:

代码语言:javascript复制
Bagels, a deductive logic game.
By Al Sweigart email@protected
I am thinking of a 3-digit number. Try to guess what it is.
Here are some clues:
When I say:    That means:
  Pico         One digit is correct but in the wrong position.
  Fermi        One digit is correct and in the right position.
  Bagels       No digit is correct.
I have thought up a number.
 You have 10 guesses to get it.
Guess #1:
> 123
Pico
Guess #2:
> 456
Bagels
Guess #3:
> 178
Pico Pico
`--snip--`
Guess #7:
> 791
Fermi Fermi
Guess #8:
> 701
You got it!
Do you want to play again? (yes or no)
> no
Thanks for playing!

工作原理

请记住,这个程序使用的不是整数值,而是包含数字的字符串值。例如,'426'是与426不同的值。我们需要这样做,因为我们执行的是与秘密数字的字符串比较,而不是数学运算。记住'0'可以是前导数字:字符串'026''26'不同,但整数02626相同。

代码语言:javascript复制
"""Bagels, by Al Sweigart email@protected
A deductive logic game where you must guess a number based on clues.
This code is available at https://nostarch.com/big-book-small-python-programming
A version of this game is featured in the book, "Invent Your Own
Computer Games with Python" https://nostarch.com/inventwithpython
Tags: short, game, puzzle"""

import random

NUM_DIGITS = 3  # (!) Try setting this to 1 or 10.
MAX_GUESSES = 10  # (!) Try setting this to 1 or 100.


def main():
   print('''Bagels, a deductive logic game.
By Al Sweigart email@protected

I am thinking of a {}-digit number with no repeated digits.
Try to guess what it is. Here are some clues:
When I say:    That means:
 Pico         One digit is correct but in the wrong position.
 Fermi        One digit is correct and in the right position.
 Bagels       No digit is correct.

For example, if the secret number was 248 and your guess was 843, the
clues would be Fermi Pico.'''.format(NUM_DIGITS))

   while True:  # Main game loop.
       # This stores the secret number the player needs to guess:
       secretNum = getSecretNum()
       print('I have thought up a number.')
       print(' You have {} guesses to get it.'.format(MAX_GUESSES))

       numGuesses = 1
       while numGuesses <= MAX_GUESSES:
           guess = ''
           # Keep looping until they enter a valid guess:
           while len(guess) != NUM_DIGITS or not guess.isdecimal():
               print('Guess #{}: '.format(numGuesses))
               guess = input('> ')

           clues = getClues(guess, secretNum)
           print(clues)
           numGuesses  = 1

           if guess == secretNum:
               break  # They're correct, so break out of this loop.
           if numGuesses > MAX_GUESSES:
               print('You ran out of guesses.')
               print('The answer was {}.'.format(secretNum))

       # Ask player if they want to play again.
       print('Do you want to play again? (yes or no)')
       if not input('> ').lower().startswith('y'):
           break
   print('Thanks for playing!')


def getSecretNum():
   """Returns a string made up of NUM_DIGITS unique random digits."""
   numbers = list('0123456789')  # Create a list of digits 0 to 9.
   random.shuffle(numbers)  # Shuffle them into random order.

   # Get the first NUM_DIGITS digits in the list for the secret number:
   secretNum = ''
   for i in range(NUM_DIGITS):
       secretNum  = str(numbers[i])
   return secretNum


def getClues(guess, secretNum):
   """Returns a string with the pico, fermi, bagels clues for a guess
   and secret number pair."""
   if guess == secretNum:
       return 'You got it!'

   clues = []

   for i in range(len(guess)):
       if guess[i] == secretNum[i]:
           # A correct digit is in the correct place.
           clues.append('Fermi')
       elif guess[i] in secretNum:
           # A correct digit is in the incorrect place.
           clues.append('Pico')
   if len(clues) == 0:
       return 'Bagels'  # There are no correct digits at all.
   else:
       # Sort the clues into alphabetical order so their original order
       # doesn't give information away.
       clues.sort()
       # Make a single string from the list of string clues.
       return ' '.join(clues)


# If the program is run (instead of imported), run the game:
if __name__ == '__main__':
   main() 

在输入源代码并运行几次之后,尝试对其进行实验性的修改。标有(!)的注释对你可以做的小改变有建议。你也可以自己想办法做到以下几点:

  • 通过改变NUM_DIGITS来改变密码的位数。
  • 通过改变MAX_GUESSES来改变玩家获得的猜测次数。
  • 尝试创建一个密码中包含字母和数字的版本。

探索程序

试着找出下列问题的答案。尝试对代码进行一些修改,然后重新运行程序,看看这些修改有什么影响。

  1. 改变NUM_DIGITS常数会发生什么?
  2. 改变MAX_GUESSES常数会发生什么?
  3. 如果将NUM_DIGITS设置为大于10的数字会发生什么?
  4. 如果把第 30 行的secretNum = getSecretNum()换成secretNum = '123'会怎么样?
  5. 如果删除或注释掉第 34 行的numGuesses = 1,会得到什么错误信息?
  6. 如果删除或注释掉第 62 行的random.shuffle(numbers)会发生什么?
  7. 如果删除或注释掉第 74 行的if guess == secretNum:和第 75 行的return 'You got it!'会怎么样?
  8. 如果在第 44 行注释掉numGuesses = 1会发生什么?

二、生日悖论

原文:http://inventwithpython.com/bigbookpython/project2.html 生日悖论(Birthday Paradox),也称为生日问题,是指即使在一小群人中,两个人过同一个生日的概率也高得惊人。在一个 70 人的小组中,有 99.9%的可能性两个人有相同的生日。但是,即使在一个只有 23 人的小组中,也有 50%的机会有相同的生日。这个程序执行几个概率实验来确定不同大小的组的百分比。我们称这种类型的实验为蒙特卡洛实验,在这种实验中,我们进行多次随机试验来了解可能的结果。

你可以在en.wikipedia.org/wiki/Birthday_problem找到更多关于生日悖论的信息。

运行示例

当您运行birthdayparadox.py时,输出如下:

代码语言:javascript复制
Birthday Paradox, by Al Sweigart email@protected
`--snip--`
How many birthdays shall I generate? (Max 100)
> 23
Here are 23 birthdays:
Oct 9, Sep 1, May 28, Jul 29, Feb 17, Jan 8, Aug 18, Feb 19, Dec 1, Jan 22,
May 16, Sep 25, Oct 6, May 6, May 26, Oct 11, Dec 19, Jun 28, Jul 29, Dec 6,
Nov 26, Aug 18, Mar 18
In this simulation, multiple people have a birthday on Jul 29
Generating 23 random birthdays 100,000 times...
Press Enter to begin...
Let's run another 100,000 simulations.
0 simulations run...
10000 simulations run...
`--snip--`
90000 simulations run...
100000 simulations run.
Out of 100,000 simulations of 23 people, there was a
matching birthday in that group 50955 times. This means
that 23 people have a 50.95 % chance of
having a matching birthday in their group.
That's probably more than you would think!

工作原理

运行 100,000 次模拟可能需要一段时间,这就是为什么第 95 行和第 96 行报告另外 10,000 次模拟已经完成。这个反馈可以向用户保证程序没有冻结。注意一些整数,比如第 95 行的10_000和第 93、103 行的100_000,有下划线。这些下划线没有特殊的含义,但是 Python 允许使用它们,这样程序员可以使整数值更容易阅读。换句话说,读100_000的《十万》比读100000的更容易。

代码语言:javascript复制
"""Birthday Paradox Simulation, by Al Sweigart email@protected
Explore the surprising probabilities of the "Birthday Paradox".
More info at https://en.wikipedia.org/wiki/Birthday_problem
This code is available at https://nostarch.com/big-book-small-python-programming
Tags: short, math, simulation"""

import datetime, random


def getBirthdays(numberOfBirthdays):
   """Returns a list of number random date objects for birthdays."""
   birthdays = []
   for i in range(numberOfBirthdays):
       # The year is unimportant for our simulation, as long as all
       # birthdays have the same year.
       startOfYear = datetime.date(2001, 1, 1)

       # Get a random day into the year:
       randomNumberOfDays = datetime.timedelta(random.randint(0, 364))
       birthday = startOfYear   randomNumberOfDays
       birthdays.append(birthday)
   return birthdays


def getMatch(birthdays):
   """Returns the date object of a birthday that occurs more than once
   in the birthdays list."""
   if len(birthdays) == len(set(birthdays)):
       return None  # All birthdays are unique, so return None.

   # Compare each birthday to every other birthday:
   for a, birthdayA in enumerate(birthdays):
       for b, birthdayB in enumerate(birthdays[a   1 :]):
           if birthdayA == birthdayB:
               return birthdayA  # Return the matching birthday.


# Display the intro:
print('''Birthday Paradox, by Al Sweigart email@protected

The birthday paradox shows us that in a group of N people, the odds
that two of them have matching birthdays is surprisingly large.
This program does a Monte Carlo simulation (that is, repeated random
simulations) to explore this concept.

(It's not actually a paradox, it's just a surprising result.)
''')

# Set up a tuple of month names in order:
MONTHS = ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
         'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec')

while True:  # Keep asking until the user enters a valid amount.
   print('How many birthdays shall I generate? (Max 100)')
   response = input('> ')
   if response.isdecimal() and (0 < int(response) <= 100):
       numBDays = int(response)
       break  # User has entered a valid amount.
print()

# Generate and display the birthdays:
print('Here are', numBDays, 'birthdays:')
birthdays = getBirthdays(numBDays)
for i, birthday in enumerate(birthdays):
   if i != 0:
       # Display a comma for each birthday after the first birthday.
       print(', ', end='')
   monthName = MONTHS[birthday.month - 1]
   dateText = '{}  {}'.format(monthName, birthday.day)
   print(dateText, end='')
print()
print()

# Determine if there are two birthdays that match.
match = getMatch(birthdays)

# Display the results:
print('In this simulation, ', end='')
if match != None:
   monthName = MONTHS[match.month - 1]
   dateText = '{}  {}'.format(monthName, match.day)
   print('multiple people have a birthday on', dateText)
else:
   print('there are no matching birthdays.')
print()

# Run through 100,000 simulations:
print('Generating', numBDays, 'random birthdays 100,000 times...')
input('Press Enter to begin...')

print('Let's run another 100,000 simulations.')
simMatch = 0  # How many simulations had matching birthdays in them.
for i in range(100000):
   # Report on the progress every 10,000 simulations:
   if i % 10000 == 0:
       print(i, 'simulations run...')
   birthdays = getBirthdays(numBDays)
   if getMatch(birthdays) != None:
       simMatch = simMatch   1
print('100,000 simulations run.')

# Display simulation results:
probability = round(simMatch / 100000 * 100, 2)
print('Out of 100,000 simulations of', numBDays, 'people, there was a')
print('matching birthday in that group', simMatch, 'times. This means')
print('that', numBDays, 'people have a', probability, '% chance of')
print('having a matching birthday in their group.')
print('That's probably more than you would think!') 

探索程序

试着找出下列问题的答案。尝试对代码进行一些修改,然后重新运行程序,看看这些修改有什么影响。

  1. 生日在这个节目中是如何表示的?(提示:请看第 16 行。)
  2. 你如何取消程序生成的 100 个生日的最大限制?
  3. 如果删除或注释掉第 57 行的numBDays = int(response),会得到什么错误信息?
  4. 如何让程序显示完整的月份名称,比如'January'而不是'Jan'
  5. 你如何让'X simulations run...'每 1000 次模拟出现一次,而不是每 10000 次?

三、位图消息

原文:http://inventwithpython.com/bigbookpython/project3.html 这个程序使用一个多行字符串作为位图,一个每个像素只有两种可能颜色的 2D 图像,来决定它应该如何显示来自用户的消息。在这个位图中,空格字符代表一个空白,所有其他字符都被用户消息中的字符所替换。提供的位图类似于世界地图,但是您可以将其更改为您喜欢的任何图像。空格或消息字符系统的二进制简单性使它非常适合初学者。尝试不同的消息,看看结果是什么样的!

运行示例

当您运行bitmapmessage.py时,输出将如下所示:

代码语言:javascript复制
Bitmap Message, by Al Sweigart email@protected
Enter the message to display with the bitmap.
> Hello!

Hello!Hello!Hello!Hello!Hello!Hello!Hello!Hello!Hello!Hello!Hello!He
   lo!Hello!Hello   l  !He lo  e      llo!Hello!Hello!Hello!Hello!He
  llo!Hello!Hello!Hello He lo H  l !Hello!Hello!Hello!Hello!Hello H
 el      lo!Hello!Hello!He       lo!Hello!Hello!Hello!Hello!Hel
          o!Hello!Hello          lo  e lo!H ll !Hello!Hello!H l
           !Hello!He            llo!Hel   Hello!Hello!Hell ! e
            Hello!He           ello!Hello!Hello!Hello!Hell  H
   l        H llo! ell         ello!Hello!Hell !Hello  el o
               lo!H  l         ello!Hello!Hell   ell !He  o
                 !Hello         llo!Hello!Hel    el   He  o
                 !Hello!H        lo!Hello!Hell    l  !H llo
                   ello!Hel         Hello!He          H llo Hell
                   ello!Hell         ello!H  l        Hell !H l o!
                   ello!Hell         ello!H l o           o!H l   H
                     lo!Hel          ello! el             o!Hel   H
                     lo!He            llo! e            llo!Hell
                    llo!H             llo!              llo!Hello
                    llo!              ll                 lo!Hell   e
                    llo                                       l    e
                    ll     l                    H
Hello!Hello!Hello!Hello!Hello!Hello!Hello!Hello!Hello!Hello!Hello!He 

工作原理

你可以从inventwithpython.com/bitmapworld.txt复制并粘贴整个世界地图图案,而不是单独输入每个字符。图案顶部和底部的 68 个句点组成的线充当标尺,帮助您正确对齐。然而,如果您在模式中输入了错误,该程序仍然可以工作。

第 43 行的bitmap.splitlines()方法调用返回一个字符串列表,每个字符串都是多行bitmap字符串中的一行。使用多行字符串使位图更容易编辑成您喜欢的任何图案。该程序填充模式中的任何非空格字符,这就是星号、句点或任何其他字符做同样事情的原因。

第 51 行的message[i % len(message)]代码导致message中文本的重复。随着i0增加到大于len(message)的数字,表达式i % len(message)再次计算为0。这使得message[i % len(message)]随着i的增加而重复message中的字符。

代码语言:javascript复制
"""Bitmap Message, by Al Sweigart email@protected
Displays a text message according to the provided bitmap image.
This code is available at https://nostarch.com/big-book-small-python-programming
Tags: tiny, beginner, artistic"""

import sys

# (!) Try changing this multiline string to any image you like:

# There are 68 periods along the top and bottom of this string:
# (You can also copy and paste this string from
# https://inventwithpython.com/bitmapworld.txt)
bitmap = """
....................................................................
   **************   *  *** **  *      ******************************
  ********************* ** ** *  * ****************************** *
 **      *****************       ******************************
          *************          **  * **** ** ************** *
           *********            *******   **************** * *
            ********           ***************************  *
   *        * **** ***         *************** ******  ** *
               ****  *         ***************   *** ***  *
                 ******         *************    **   **  *
                 ********        *************    *  ** ***
                   ********         ********          * *** ****
                   *********         ******  *        **** ** * **
                   *********         ****** * *           *** *   *
                     ******          ***** **             *****   *
                     *****            **** *            ********
                    *****             ****              *********
                    ****              **                 *******   *
                    ***                                       *    *
                    **     *                    *
...................................................................."""

print('Bitmap Message, by Al Sweigart email@protected')
print('Enter the message to display with the bitmap.')
message = input('> ')
if message == '':
    sys.exit()

# Loop over each line in the bitmap:
for line in bitmap.splitlines():
    # Loop over each character in the line:
    for i, bit in enumerate(line):
        if bit == ' ':
            # Print an empty space since there's a space in the bitmap:
            print(' ', end='')
        else:
            # Print a character from the message:
            print(message[i % len(message)], end='')
    print()  # Print a newline. 

在输入源代码并运行几次之后,尝试对其进行实验性的修改。您可以更改bitmap中的字符串来创建全新的模式。

探索程序

试着找出下列问题的答案。尝试对代码进行一些修改,然后重新运行程序,看看这些修改有什么影响。

  1. 如果玩家为消息输入一个空字符串会发生什么?
  2. bitmap变量的字符串中有什么非空格字符有关系吗?
  3. 第 45 行创建的i变量代表什么?
  4. 如果删除或注释掉第 52 行的print(),会出现什么 bug?

四、二十一点

原文:http://inventwithpython.com/bigbookpython/project4.html

21 点,也称为 21 点,是一种纸牌游戏,玩家试图在不超过 21 点的情况下尽可能接近 21 点。这个程序使用用文本字符绘制的图像,称为 ASCII 艺术画。美国信息交换标准码(ASCII)是 Unicode 取代之前计算机使用的文本字符到数字代码的映射。本程序中的扑克牌是 ASCII 艺术画的一个例子:
代码语言:javascript复制
 ___   ___
|A  | |10 |
| ♣ | | ♦ |
|__A| |_10|

你可以在en.wikipedia.org/wiki/Blackjack找到其他规则和这个纸牌游戏的历史。

运行示例

当您运行blackjack.py时,输出将如下所示:

代码语言:javascript复制
Blackjack, by Al Sweigart email@protected

    Rules:
      Try to get as close to 21 without going over.
      Kings, Queens, and Jacks are worth 10 points.
      Aces are worth 1 or 11 points.
      Cards 2 through 10 are worth their face value.
      (H)it to take another card.
      (S)tand to stop taking cards.
      On your first play, you can (D)ouble down to increase your bet
      but must hit exactly one more time before standing.
      In case of a tie, the bet is returned to the player.
      The dealer stops hitting at 17.
Money: 5000
How much do you bet? (1-5000, or QUIT)
> 400
Bet: 400

DEALER: ???
 ___   ___
|## | |2  |
|###| | ♥ |
|_##| |__2|

PLAYER: 17
 ___   ___
|K  | |7  |
| ♠ | | ♦ |
|__K| |__7|

(H)it, (S)tand, (D)ouble down
> h
You drew a 4 of ♦.
`--snip--`
DEALER: 18
 ___   ___   ___
|K  | |2  | |6  |
| ♦ | | ♥ | | ♠ |
|__K| |__2| |__6|

PLAYER: 21
 ___   ___   ___
|K  | |7  | |4  |
| ♠ | | ♦ | | ♦ |
|__K| |__7| |__4|

You won $400!
`--snip—`

工作原理

你的键盘上没有牌组符号,这就是为什么我们调用chr()函数来创建它们。传递给chr()的整数被称为 Unicode 码位,这是根据 Unicode 标准标识字符的唯一数字。Unicode 经常被误解。然而,Ned Batchelder 的 2012 年 PyCon US talk“实用 Unicode,或者我如何停止痛苦?”是对 Unicode 的极好介绍,您可以在youtu.be/sgHbC6udIqc找到它。附录 B 给出了可以在 Python 程序中使用的 Unicode 字符的完整列表。

代码语言:javascript复制
"""Blackjack, by Al Sweigart email@protected
The classic card game also known as 21. (This version doesn't have
splitting or insurance.)
More info at: https://en.wikipedia.org/wiki/Blackjack
This code is available at https://nostarch.com/big-book-small-python-programming
Tags: large, game, card game"""

import random, sys

# Set up the constants:
HEARTS   = chr(9829) # Character 9829 is '♥'.
DIAMONDS = chr(9830) # Character 9830 is '♦'.
SPADES   = chr(9824) # Character 9824 is '♠'.
CLUBS    = chr(9827) # Character 9827 is '♣'.
# (A list of chr codes is at https://inventwithpython.com/charactermap)
BACKSIDE = 'backside'


def main():
   print('''Blackjack, by Al Sweigart email@protected

   Rules:
     Try to get as close to 21 without going over.
     Kings, Queens, and Jacks are worth 10 points.
     Aces are worth 1 or 11 points.
     Cards 2 through 10 are worth their face value.
     (H)it to take another card.
     (S)tand to stop taking cards.
     On your first play, you can (D)ouble down to increase your bet
     but must hit exactly one more time before standing.
     In case of a tie, the bet is returned to the player.
     The dealer stops hitting at 17.''')

   money = 5000
   while True:  # Main game loop.
       # Check if the player has run out of money:
       if money <= 0:
           print("You're broke!")
           print("Good thing you weren't playing with real money.")
           print('Thanks for playing!')
           sys.exit()

       # Let the player enter their bet for this round:
       print('Money:', money)
       bet = getBet(money)

       # Give the dealer and player two cards from the deck each:
       deck = getDeck()
       dealerHand = [deck.pop(), deck.pop()]
       playerHand = [deck.pop(), deck.pop()]

       # Handle player actions:
       print('Bet:', bet)
       while True:  # Keep looping until player stands or busts.
           displayHands(playerHand, dealerHand, False)
           print()

           # Check if the player has bust:
           if getHandValue(playerHand) > 21:
               break

           # Get the player's move, either H, S, or D:
           move = getMove(playerHand, money - bet)

           # Handle the player actions:
           if move == 'D':
               # Player is doubling down, they can increase their bet:
               additionalBet = getBet(min(bet, (money - bet)))
               bet  = additionalBet
               print('Bet increased to {}.'.format(bet))
               print('Bet:', bet)

           if move in ('H', 'D'):
               # Hit/doubling down takes another card.
               newCard = deck.pop()
               rank, suit = newCard
               print('You drew a {} of {}.'.format(rank, suit))
               playerHand.append(newCard)

               if getHandValue(playerHand) > 21:
                   # The player has busted:
                   continue

           if move in ('S', 'D'):
               # Stand/doubling down stops the player's turn.
               break

       # Handle the dealer's actions:
       if getHandValue(playerHand) <= 21:
           while getHandValue(dealerHand) < 17:
               # The dealer hits:
               print('Dealer hits...')
               dealerHand.append(deck.pop())
               displayHands(playerHand, dealerHand, False)

               if getHandValue(dealerHand) > 21:
                   break  # The dealer has busted.
               input('Press Enter to continue...')
               print('nn')

        # Show the final hands:
        displayHands(playerHand, dealerHand, True)

        playerValue = getHandValue(playerHand)
        dealerValue = getHandValue(dealerHand)
        # Handle whether the player won, lost, or tied:
        if dealerValue > 21:
            print('Dealer busts! You win ${}!'.format(bet))
            money  = bet
        elif (playerValue > 21) or (playerValue < dealerValue):
            print('You lost!')
            money -= bet
        elif playerValue > dealerValue:
            print('You won ${}!'.format(bet))
            money  = bet
        elif playerValue == dealerValue:
            print('It's a tie, the bet is returned to you.')

        input('Press Enter to continue...')
        print('nn')


def getBet(maxBet):
    """Ask the player how much they want to bet for this round."""
    while True:  # Keep asking until they enter a valid amount.
        print('How much do you bet? (1-{}, or QUIT)'.format(maxBet))
        bet = input('> ').upper().strip()
        if bet == 'QUIT':
            print('Thanks for playing!')
            sys.exit()

        if not bet.isdecimal():
            continue  # If the player didn't enter a number, ask again.

        bet = int(bet)
        if 1 <= bet <= maxBet:
            return bet  # Player entered a valid bet.


def getDeck():
    """Return a list of (rank, suit) tuples for all 52 cards."""
    deck = []
    for suit in (HEARTS, DIAMONDS, SPADES, CLUBS):
        for rank in range(2, 11):
            deck.append((str(rank), suit))  # Add the numbered cards.
        for rank in ('J', 'Q', 'K', 'A'):
            deck.append((rank, suit))  # Add the face and ace cards.
    random.shuffle(deck)
    return deck


def displayHands(playerHand, dealerHand, showDealerHand):
    """Show the player's and dealer's cards. Hide the dealer's first
    card if showDealerHand is False."""
    print()
    if showDealerHand:
        print('DEALER:', getHandValue(dealerHand))
        displayCards(dealerHand)
    else:
        print('DEALER: ???')
        # Hide the dealer's first card:
        displayCards([BACKSIDE]   dealerHand[1:])

    # Show the player's cards:
    print('PLAYER:', getHandValue(playerHand))
    displayCards(playerHand)


def getHandValue(cards):
    """Returns the value of the cards. Face cards are worth 10, aces are
    worth 11 or 1 (this function picks the most suitable ace value)."""
    value = 0
    numberOfAces = 0

    # Add the value for the non-ace cards:
    for card in cards:
        rank = card[0]  # card is a tuple like (rank, suit)
        if rank == 'A':
            numberOfAces  = 1
        elif rank in ('K', 'Q', 'J'):  # Face cards are worth 10 points.
            value  = 10
        else:
            value  = int(rank)  # Numbered cards are worth their number.

    # Add the value for the aces:
    value  = numberOfAces  # Add 1 per ace.
    for i in range(numberOfAces):
        # If another 10 can be added without busting, do so:
        if value   10 <= 21:
            value  = 10

    return value


def displayCards(cards):
    """Display all the cards in the cards list."""
    rows = ['', '', '', '', '']  # The text to display on each row.

    for i, card in enumerate(cards):
        rows[0]  = ' ___  '  # Print the top line of the card.
        if card == BACKSIDE:
            # Print a card's back:
            rows[1]  = '|## | '
            rows[2]  = '|###| '
            rows[3]  = '|_##| '
        else:
            # Print the card's front:
            rank, suit = card  # The card is a tuple data structure.
            rows[1]  = '|{} | '.format(rank.ljust(2))
            rows[2]  = '| {} | '.format(suit)
            rows[3]  = '|_{}| '.format(rank.rjust(2, '_'))

    # Print each row on the screen:
    for row in rows:
        print(row)


def getMove(playerHand, money):
    """Asks the player for their move, and returns 'H' for hit, 'S' for
    stand, and 'D' for double down."""
    while True:  # Keep looping until the player enters a correct move.
        # Determine what moves the player can make:
        moves = ['(H)it', '(S)tand']

        # The player can double down on their first move, which we can
        # tell because they'll have exactly two cards:
        if len(playerHand) == 2 and money > 0:
            moves.append('(D)ouble down')

        # Get the player's move:
        movePrompt = ', '.join(moves)   '> '
        move = input(movePrompt).upper()
        if move in ('H', 'S'):
            return move  # Player has entered a valid move.
        if move == 'D' and '(D)ouble down' in moves:
            return move  # Player has entered a valid move.


# If the program is run (instead of imported), run the game:
if __name__ == '__main__':
    main() 

在输入源代码并运行几次之后,尝试对其进行实验性的修改。21 点有几个自定义规则,你可以实现。例如,如果前两张牌的价值相同,玩家可以将它们分成两只手,分别下注。此外,如果玩家的前两张牌中有一张“21 点”(黑桃 A 和 21 点),玩家将赢得十比一的奖金。你可以从en.wikipedia.org/wiki/Blackjack那里找到更多关于这个游戏的信息。

探索程序

试着找出下列问题的答案。尝试对代码进行一些修改,然后重新运行程序,看看这些修改有什么影响。

  1. 你如何让玩家以不同的金额开始游戏?
  2. 该计划如何防止玩家下注超过他们的钱?
  3. 程序如何表示单卡?
  4. 该程序如何表示一手牌?
  5. rows列表中的每个字符串(在第 197 行创建)代表什么?
  6. 如果删除或注释掉第 148 行的random.shuffle(deck)会发生什么?
  7. 如果把 112 行的money -= bet改成money = bet会怎么样?
  8. displayHands()函数中的showDealerHand设置为True时会发生什么?到了False会怎么样?

五、弹跳 DVD 标志

原文:http://inventwithpython.com/bigbookpython/project5.html 如果你到了一定的年龄,你会记得那些被称为 DVD 播放器的古老技术设备。当不播放 DVD 时,他们会显示一个从屏幕边缘反弹回来的对角线方向的 DVD 标志。这个程序通过每次碰到边缘时改变方向来模拟这个彩色的 DVD 标志。我们还将记录一个标志点击屏幕一角的次数。这创造了一个有趣的视觉动画,特别是当一个标志与一个角完美地对齐的神奇时刻。

您不能从您的集成开发环境(IDE)或编辑器运行此程序,因为它使用了bext模块。因此,必须从命令提示符或终端运行它才能正确显示。你可以在pypi.org/project/bext找到更多关于bext模块的信息。

运行示例

当你运行bouncingdvd.py时,输出将看起来像图 5-1 。

bouncingdvd.py节目的斜向移动的 DVD 标识

工作原理

你可能还记得学校数学课上的笛卡尔坐标。在编程中,x 坐标表示对象的水平位置,y 坐标表示其垂直位置,就像数学中一样。然而,与数学中不同的是,原点(0, 0)在屏幕的左上角,y 坐标随着向下移动而增加。x 坐标随着对象向右移动而增加,就像数学中一样。图 5-2 显示了屏幕的坐标系统。

:原点(0,0)在屏幕的左上方,x 和 y 坐标分别向右下递增。

bext模块的goto()函数工作方式相同:调用bext.goto(0, 0)将文本光标放在终端窗口的左上角。我们使用一个 Python 字典,用关键字'color''direction''x''y'来表示每个跳动的 DVD 标志。'x''y'的值是代表窗口中徽标位置的整数。由于这些值被传递到bext.goto(),增加它们将使徽标向右和向下移动,而减少它们将使徽标向左和向上移动。

代码语言:javascript复制
"""Bouncing DVD Logo, by Al Sweigart email@protected
A bouncing DVD logo animation. You have to be "of a certain age" to
appreciate this. Press Ctrl-C to stop.

NOTE: Do not resize the terminal window while this program is running.
This code is available at https://nostarch.com/big-book-small-python-programming
Tags: short, artistic, bext"""

import sys, random, time

try:
   import bext
except ImportError:
   print('This program requires the bext module, which you')
   print('can install by following the instructions at')
   print('https://pypi.org/project/Bext/')
   sys.exit()

# Set up the constants:
WIDTH, HEIGHT = bext.size()
# We can't print to the last column on Windows without it adding a
# newline automatically, so reduce the width by one:
WIDTH -= 1

NUMBER_OF_LOGOS = 5  # (!) Try changing this to 1 or 100.
PAUSE_AMOUNT = 0.2  # (!) Try changing this to 1.0 or 0.0.
# (!) Try changing this list to fewer colors:
COLORS = ['red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white']

UP_RIGHT   = 'ur'
UP_LEFT    = 'ul'
DOWN_RIGHT = 'dr'
DOWN_LEFT  = 'dl'
DIRECTIONS = (UP_RIGHT, UP_LEFT, DOWN_RIGHT, DOWN_LEFT)

# Key names for logo dictionaries:
COLOR = 'color'
X = 'x'
Y = 'y'
DIR = 'direction'


def main():
   bext.clear()

   # Generate some logos.
   logos = []
   for i in range(NUMBER_OF_LOGOS):
       logos.append({COLOR: random.choice(COLORS),
                     X: random.randint(1, WIDTH - 4),
                     Y: random.randint(1, HEIGHT - 4),
                     DIR: random.choice(DIRECTIONS)})
       if logos[-1][X] % 2 == 1:
           # Make sure X is even so it can hit the corner.
           logos[-1][X] -= 1

   cornerBounces = 0  # Count how many times a logo hits a corner.
   while True:  # Main program loop.
       for logo in logos:  # Handle each logo in the logos list.
           # Erase the logo's current location:
           bext.goto(logo[X], logo[Y])
           print('   ', end='')  # (!) Try commenting this line out.

           originalDirection = logo[DIR]

           # See if the logo bounces off the corners:
           if logo[X] == 0 and logo[Y] == 0:
               logo[DIR] = DOWN_RIGHT
               cornerBounces  = 1
           elif logo[X] == 0 and logo[Y] == HEIGHT - 1:
               logo[DIR] = UP_RIGHT
               cornerBounces  = 1
           elif logo[X] == WIDTH - 3 and logo[Y] == 0:
               logo[DIR] = DOWN_LEFT
               cornerBounces  = 1
           elif logo[X] == WIDTH - 3 and logo[Y] == HEIGHT - 1:
               logo[DIR] = UP_LEFT
               cornerBounces  = 1

           # See if the logo bounces off the left edge:
           elif logo[X] == 0 and logo[DIR] == UP_LEFT:
               logo[DIR] = UP_RIGHT
           elif logo[X] == 0 and logo[DIR] == DOWN_LEFT:
               logo[DIR] = DOWN_RIGHT

           # See if the logo bounces off the right edge:
           # (WIDTH - 3 because 'DVD' has 3 letters.)
           elif logo[X] == WIDTH - 3 and logo[DIR] == UP_RIGHT:
               logo[DIR] = UP_LEFT
           elif logo[X] == WIDTH - 3 and logo[DIR] == DOWN_RIGHT:
               logo[DIR] = DOWN_LEFT

           # See if the logo bounces off the top edge:
           elif logo[Y] == 0 and logo[DIR] == UP_LEFT:
               logo[DIR] = DOWN_LEFT
           elif logo[Y] == 0 and logo[DIR] == UP_RIGHT:
               logo[DIR] = DOWN_RIGHT

           # See if the logo bounces off the bottom edge:
            elif logo[Y] == HEIGHT - 1 and logo[DIR] == DOWN_LEFT:
                logo[DIR] = UP_LEFT
            elif logo[Y] == HEIGHT - 1 and logo[DIR] == DOWN_RIGHT:
                logo[DIR] = UP_RIGHT

            if logo[DIR] != originalDirection:
                # Change color when the logo bounces:
                logo[COLOR] = random.choice(COLORS)

            # Move the logo. (X moves by 2 because the terminal
            # characters are twice as tall as they are wide.)
            if logo[DIR] == UP_RIGHT:
                logo[X]  = 2
                logo[Y] -= 1
            elif logo[DIR] == UP_LEFT:
                logo[X] -= 2
                logo[Y] -= 1
            elif logo[DIR] == DOWN_RIGHT:
                logo[X]  = 2
                logo[Y]  = 1
            elif logo[DIR] == DOWN_LEFT:
                logo[X] -= 2
                logo[Y]  = 1

        # Display number of corner bounces:
        bext.goto(5, 0)
        bext.fg('white')
        print('Corner bounces:', cornerBounces, end='')

        for logo in logos:
            # Draw the logos at their new location:
            bext.goto(logo[X], logo[Y])
            bext.fg(logo[COLOR])
            print('DVD', end='')

        bext.goto(0, 0)

        sys.stdout.flush()  # (Required for bext-using programs.)
        time.sleep(PAUSE_AMOUNT)


# If this program was run (instead of imported), run the game:
if __name__ == '__main__':
    try:
        main()
    except KeyboardInterrupt:
        print()
        print('Bouncing DVD Logo, by Al Sweigart')
        sys.exit()  # When Ctrl-C is pressed, end the program. 

在输入源代码并运行几次之后,尝试对其进行实验性的修改。标有(!)的注释对你可以做的小改变有建议。你也可以自己想办法做到以下几点:

  • 更改NUMBER_OF_LOGOS以增加屏幕上跳跃标志的数量。
  • 更改PAUSE_AMOUNT以加快或减慢徽标的速度。

探索程序

试着找出下列问题的答案。尝试对代码进行一些修改,然后重新运行程序,看看这些修改有什么影响。

  1. 如果把第 20 行的WIDTH, HEIGHT = bext.size()改成WIDTH, HEIGHT = 10, 5会怎么样?
  2. 如果把第 52 行的DIR: random.choice(DIRECTIONS)换成DIR: DOWN_RIGHT会怎么样?
  3. 如何让'Corner bounces:'文字不出现在屏幕上?
  4. 如果删除或注释掉第 57 行的cornerBounces = 0,会得到什么错误信息?

0 人点赞