六、凯撒密码
凯撒密码是朱利叶斯·凯撒使用的一种古老的加密算法。它通过将字母在字母表中移动一定的位置来加密字母。我们称移位的长度为密钥。比如,如果密钥是 3,那么A
变成D
,B
变成E
,C
变成F
,以此类推。要解密信息,你必须将加密的字母向相反的方向移动。这个程序让用户根据这个算法加密和解密信息。
在现代,凯撒密码不是很复杂,但这使它成为初学者的理想选择。Project 7 中的程序“凯撒破解”可以暴力破解所有 26 个可能的密钥来解密消息,即使你不知道原始密钥。此外,如果您使用密钥 13 对消息进行加密,凯撒密码将与项目 61 的“ROT 13 密码”相同。在en.wikipedia.org/wiki/Caesar_cipher
了解更多关于凯撒密码的信息。如果你想学习一般的密码和密码破解,你可以阅读我的书《Python 密码破解指南》(NoStarch 出版社,2018)。
运行示例
当您运行caesarcipher.py
时,输出将如下所示:
Caesar Cipher, by Al Sweigart email@protected
Do you want to (e)ncrypt or (d)ecrypt?
> e
Please enter the key (0 to 25) to use.
> 4
Enter the message to encrypt.
> Meet me by the rose bushes tonight.
QIIX QI FC XLI VSWI FYWLIW XSRMKLX.
Full encrypted text copied to clipboard.
Caesar Cipher, by Al Sweigart email@protected
Do you want to (e)ncrypt or (d)ecrypt?
> d
Please enter the key (0 to 26) to use.
> 4
Enter the message to decrypt.
> QIIX QI FC XLI VSWI FYWLIW XSRMKLX.
MEET ME BY THE ROSE BUSHES TONIGHT.
Full decrypted text copied to clipboard.
工作原理
像大多数密码程序一样,凯撒密码的工作原理是将字符翻译成数字,对这些数字执行一些数学运算,然后将数字翻译回文本字符。在密码的上下文中,我们称这些文本字符为符号。符号可以包括字母、数字和标点符号,每个符号都被赋予一个唯一的整数。在凯撒密码程序的情况下,符号都是字母,它们的整数就是它们在SYMBOLS
字符串:'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
中的位置。
"""Caesar Cipher, by Al Sweigart email@protected
The Caesar cipher is a shift cipher that uses addition and subtraction
to encrypt and decrypt letters.
More info at: https://en.wikipedia.org/wiki/Caesar_cipher
View this code at https://nostarch.com/big-book-small-python-projects
Tags: short, beginner, cryptography, math"""
try:
import pyperclip # pyperclip copies text to the clipboard.
except ImportError:
pass # If pyperclip is not installed, do nothing. It's no big deal.
# Every possible symbol that can be encrypted/decrypted:
# (!) You can add numbers and punctuation marks to encrypt those
# symbols as well.
SYMBOLS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
print('Caesar Cipher, by Al Sweigart email@protected')
print('The Caesar cipher encrypts letters by shifting them over by a')
print('key number. For example, a key of 2 means the letter A is')
print('encrypted into C, the letter B encrypted into D, and so on.')
print()
# Let the user enter if they are encrypting or decrypting:
while True: # Keep asking until the user enters e or d.
print('Do you want to (e)ncrypt or (d)ecrypt?')
response = input('> ').lower()
if response.startswith('e'):
mode = 'encrypt'
break
elif response.startswith('d'):
mode = 'decrypt'
break
print('Please enter the letter e or d.')
# Let the user enter the key to use:
while True: # Keep asking until the user enters a valid key.
maxKey = len(SYMBOLS) - 1
print('Please enter the key (0 to {}) to use.'.format(maxKey))
response = input('> ').upper()
if not response.isdecimal():
continue
if 0 <= int(response) < len(SYMBOLS):
key = int(response)
break
# Let the user enter the message to encrypt/decrypt:
print('Enter the message to {}.'.format(mode))
message = input('> ')
# Caesar cipher only works on uppercase letters:
message = message.upper()
# Stores the encrypted/decrypted form of the message:
translated = ''
# Encrypt/decrypt each symbol in the message:
for symbol in message:
if symbol in SYMBOLS:
# Get the encrypted (or decrypted) number for this symbol.
num = SYMBOLS.find(symbol) # Get the number of the symbol.
if mode == 'encrypt':
num = num + key
elif mode == 'decrypt':
num = num - key
# Handle the wrap-around if num is larger than the length of
# SYMBOLS or less than 0:
if num >= len(SYMBOLS):
num = num - len(SYMBOLS)
elif num < 0:
num = num + len(SYMBOLS)
# Add encrypted/decrypted number's symbol to translated:
translated = translated + SYMBOLS[num]
else:
# Just add the symbol without encrypting/decrypting:
translated = translated + symbol
# Display the encrypted/decrypted string to the screen:
print(translated)
try:
pyperclip.copy(translated)
print('Full {}ed text copied to clipboard.'.format(mode))
except:
pass # Do nothing if pyperclip wasn't installed.
在输入源代码并运行几次之后,尝试对其进行实验性的修改。标有(!)
的注释对你可以做的小改变有建议。您可以通过向SYMBOLS
字符串添加字符来扩展可加密的符号。
探索程序
试着找出下列问题的答案。尝试对代码进行一些修改,然后重新运行程序,看看这些修改有什么影响。
- 如果把第 16 行的
SYMBOLS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
改成SYMBOLS = 'ABC'
会怎么样? - 用密钥
0
加密一条消息会发生什么? - 如果删除或注释掉第 56 行的
translated = ''
,会得到什么错误信息? - 如果删除或注释掉第 45 行的
key = int(response)
,会得到什么错误信息? - 如果把第 76 行的
translated = translated + SYMBOLS[num]
改成translated = translated + symbol
会怎么样?
七、凯撒破解
这个程序可以破解来自第六个项目的用凯撒密码加密的信息,即使你不知道密钥。凯撒密码只有 26 个可能的密钥,所以计算机可以很容易地尝试所有可能的解密,并向用户显示结果。在密码学中,我们称这种技术为暴力攻击。如果你想了解更多关于密码和密码破解的知识,你可以阅读我的书《Python 密码破解指南》(NoStarch 出版社,2018)。
运行示例
当您运行caesarhacker.py
时,输出将如下所示:
Caesar Cipher Hacker, by Al Sweigart email@protected
Enter the encrypted Caesar cipher message to hack.
> QIIX QI FC XLI VSWI FYWLIW XSRMKLX.
Key #0: QIIX QI FC XLI VSWI FYWLIW XSRMKLX.
Key #1: PHHW PH EB WKH URVH EXVKHV WRQLJKW.
Key #2: OGGV OG DA VJG TQUG DWUJGU VQPKIJV.
Key #3: NFFU NF CZ UIF SPTF CVTIFT UPOJHIU.
Key #4: MEET ME BY THE ROSE BUSHES TONIGHT.
Key #5: LDDS LD AX SGD QNRD ATRGDR SNMHFGS.
Key #6: KCCR KC ZW RFC PMQC ZSQFCQ RMLGEFR.
`--snip--`
工作原理
请注意,这个程序中的第 20 行到第 36 行与凯撒密码程序中的第 55 行到第 78 行几乎相同。黑客程序实现了相同的解密代码,除了它是在一个for
循环中实现的,这个循环为每一个可能的密钥运行代码。
不幸的是,黑客程序不够复杂,无法识别何时找到了正确的密钥。它依赖于人来读取输出,并识别哪个解密产生了原始英语(或任何被加密的书面语言)。《Python 密码破解指南》(NoStarch 出版社,2018)详细介绍了如何编写 Python 代码来检测英文消息。
"""Caesar Cipher Hacker, by Al Sweigart email@protected
This programs hacks messages encrypted with the Caesar cipher by doing
a brute force attack against every possible key.
More info at:
https://en.wikipedia.org/wiki/Caesar_cipher#Breaking_the_cipher
This code is available at https://nostarch.com/big-book-small-python-programming
Tags: tiny, beginner, cryptography, math"""
print('Caesar Cipher Hacker, by Al Sweigart email@protected')
# Let the user specify the message to hack:
print('Enter the encrypted Caesar cipher message to hack.')
message = input('> ')
# Every possible symbol that can be encrypted/decrypted:
# (This must match the SYMBOLS used when encrypting the message.)
SYMBOLS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
for key in range(len(SYMBOLS)): # Loop through every possible key.
translated = ''
# Decrypt each symbol in the message:
for symbol in message:
if symbol in SYMBOLS:
num = SYMBOLS.find(symbol) # Get the number of the symbol.
num = num - key # Decrypt the number.
# Handle the wrap-around if num is less than 0:
if num < 0:
num = num + len(SYMBOLS)
# Add decrypted number's symbol to translated:
translated = translated + SYMBOLS[num]
else:
# Just add the symbol without decrypting:
translated = translated + symbol
# Display the key being tested, along with its decrypted text:
print('Key #{}: {}'.format(key, translated))
在输入源代码并运行几次之后,尝试对其进行实验性的修改。请记住,存储在SYMBOLS
变量中的字符串必须与生成加密文本的凯撒密码程序中的SYMBOLS
变量相匹配。
探索程序
试着找出下列问题的答案。尝试对代码进行一些修改,然后重新运行程序,看看这些修改有什么影响。
- 如果删除或注释掉第 20 行的
translated = ''
,会得到什么错误信息? - 如果把第 33 行的
translated = translated + SYMBOLS[num]
改成translated = translated + symbol
会怎么样? - 如果你在凯撒密码黑客程序中输入一条未加密的信息会发生什么?
八、日历制作器
这个程序为你输入的月份和年份生成可打印的月历文本文件。日期和日历在编程中是一个棘手的话题,因为有太多不同的规则来确定一个月中的天数,哪一年是闰年,以及特定日期是星期几。幸运的是,Python 的datetime
模块为您处理这些细节。这个程序主要是为月历页面生成多行字符串。
运行示例
当您运行calendarmaker.py
时,输出将如下所示:
Calendar Maker, by Al Sweigart email@protected
Enter the year for the calendar:
> 2029
Enter the month for the calendar, 1-12:
> 12
December 2029
...Sunday.....Monday....Tuesday...Wednesday...Thursday....Friday....Saturday..
+----------+----------+----------+----------+----------+----------+----------+
|25 |26 |27 |28 |29 |30 | 1 |
| | | | | | | |
| | | | | | | |
| | | | | | | |
+----------+----------+----------+----------+----------+----------+----------+
| 2 | 3 | 4 | 5 | 6 | 7 | 8 |
| | | | | | | |
| | | | | | | |
| | | | | | | |
+----------+----------+----------+----------+----------+----------+----------+
| 9 |10 |11 |12 |13 |14 |15 |
| | | | | | | |
| | | | | | | |
| | | | | | | |
+----------+----------+----------+----------+----------+----------+----------+
|16 |17 |18 |19 |20 |21 |22 |
| | | | | | | |
| | | | | | | |
| | | | | | | |
+----------+----------+----------+----------+----------+----------+----------+
|23 |24 |25 |26 |27 |28 |29 |
| | | | | | | |
| | | | | | | |
| | | | | | | |
+----------+----------+----------+----------+----------+----------+----------+
|30 |31 | 1 | 2 | 3 | 4 | 5 |
| | | | | | | |
| | | | | | | |
| | | | | | | |
+----------+----------+----------+----------+----------+----------+----------+
Saved to calendar_2029_12.txt
工作原理
注意,getCalendarFor()
函数返回给定月份和年份的日历的一个巨大的多行字符串。在这个函数中,calText
变量存储这个字符串,这个字符串添加了行、空格和日期。为了跟踪日期,currentDate
变量保存了一个datetime.date()
对象,通过增加或减少datetime.timedelta()
对象将其设置为下一个或上一个日期。你可以通过阅读《Python 自动化指南》的第 17 章来了解 Python 的日期和时间模块,automatetheboringstuff.com/2e/chapter17
。
"""Calendar Maker, by Al Sweigart email@protected
Create monthly calendars, saved to a text file and fit for printing.
This code is available at https://nostarch.com/big-book-small-python-programming
Tags: short"""
import datetime
# Set up the constants:
DAYS = ('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday',
'Friday', 'Saturday')
MONTHS = ('January', 'February', 'March', 'April', 'May', 'June', 'July',
'August', 'September', 'October', 'November', 'December')
print('Calendar Maker, by Al Sweigart email@protected')
while True: # Loop to get a year from the user.
print('Enter the year for the calendar:')
response = input('> ')
if response.isdecimal() and int(response) > 0:
year = int(response)
break
print('Please enter a numeric year, like 2023.')
continue
while True: # Loop to get a month from the user.
print('Enter the month for the calendar, 1-12:')
response = input('> ')
if not response.isdecimal():
print('Please enter a numeric month, like 3 for March.')
continue
month = int(response)
if 1 <= month <= 12:
break
print('Please enter a number from 1 to 12.')
def getCalendarFor(year, month):
calText = '' # calText will contain the string of our calendar.
# Put the month and year at the top of the calendar:
calText += (' ' * 34) + MONTHS[month - 1] + ' ' + str(year) + '\n'
# Add the days of the week labels to the calendar:
# (!) Try changing this to abbreviations: SUN, MON, TUE, etc.
calText += '...Sunday.....Monday....Tuesday...Wednesday...Thursday....Friday....Saturday..\n'
# The horizontal line string that separate weeks:
weekSeparator = ('+----------' * 7) + '+\n'
# The blank rows have ten spaces in between the | day separators:
blankRow = ('| ' * 7) + '|\n'
# Get the first date in the month. (The datetime module handles all
# the complicated calendar stuff for us here.)
currentDate = datetime.date(year, month, 1)
# Roll back currentDate until it is Sunday. (weekday() returns 6
# for Sunday, not 0.)
while currentDate.weekday() != 6:
currentDate -= datetime.timedelta(days=1)
while True: # Loop over each week in the month.
calText += weekSeparator
# dayNumberRow is the row with the day number labels:
dayNumberRow = ''
for i in range(7):
dayNumberLabel = str(currentDate.day).rjust(2)
dayNumberRow += '|' + dayNumberLabel + (' ' * 8)
currentDate += datetime.timedelta(days=1) # Go to next day.
dayNumberRow += '|\n' # Add the vertical line after Saturday.
# Add the day number row and 3 blank rows to the calendar text.
calText += dayNumberRow
for i in range(3): # (!) Try changing the 4 to a 5 or 10.
calText += blankRow
# Check if we're done with the month:
if currentDate.month != month:
break
# Add the horizontal line at the very bottom of the calendar.
calText += weekSeparator
return calText
calText = getCalendarFor(year, month)
print(calText) # Display the calendar.
# Save the calendar to a text file:
calendarFilename = 'calendar_{}_{}.txt'.format(year, month)
with open(calendarFilename, 'w') as fileObj:
fileObj.write(calText)
print('Saved to ' + calendarFilename)
在你输入代码并运行几次之后,试着从头开始重新创建这个程序,不要看这本书里的源代码。不一定要和这个程序一模一样;你可以发明你自己的版本!你也可以自己想办法做到以下几点:
- 在一些方框内添加假日文本。
- 为重复发生的事件在一些框中添加文本。
- 打印一个没有方框的“迷你”日历。
探索程序
试着找出下列问题的答案。尝试对代码进行一些修改,然后重新运行程序,看看这些修改有什么影响。
- 如何让日历显示缩写的月份?比如显示
'Jan'
而不是'January'
? - 如果删除或注释掉第 21 行的
year = int(response)
,会得到什么错误信息? - 如何让日历不在顶部显示星期几?
- 如何让程序不将日历保存到文件中?
- 如果删除或注释掉第 93 行的
print(calText)
会怎么样?
九、盒子里的胡萝卜
这是两个人类玩家玩的简单而愚蠢的诈唬游戏。每个玩家都有一个盒子。一个盒子里有一根胡萝卜,每个玩家都想得到这根胡萝卜。第一个玩家看着他们的盒子,然后告诉第二个玩家他们要么有要么没有胡萝卜。第二个玩家可以决定是否交换盒子。
代码中的 ASCII 艺术画使得输入这个程序需要一些时间(虽然复制和粘贴 ASCII 艺术画可以加快任务),但是这个项目对于初学者来说是很好的,因为它很简单,循环最少,没有定义函数。
运行示例
当您运行carrotinabox.py
时,输出将如下所示:
Carrot in a Box, by Al Sweigart email@protected
`--snip--`
Human player 1, enter your name: Alice
Human player 2, enter your name: Bob
HERE ARE TWO BOXES:
__________ __________
/ /| / /|
+---------+ | +---------+ |
| RED | | | GOLD | |
| BOX | / | BOX | /
+---------+/ +---------+/
Alice Bob
Alice, you have a RED box in front of you.
Bob, you have a GOLD box in front of you.
Press Enter to continue...
`--snip--`
When Bob has closed their eyes, press Enter...
Alice here is the inside of your box:
___VV____
| VV |
| VV |
|___||____| __________
/ || /| / /|
+---------+ | +---------+ |
| RED | | | GOLD | |
| BOX | / | BOX | /
+---------+/ +---------+/
(carrot!)
Alice Bob
Press Enter to continue...
`--snip--`
工作原理
这个程序依赖于第二个玩家闭上眼睛,这样他们就看不到第一个玩家盒子里的东西。为了不让第二个玩家在这一步之后看到盒子里的内容,我们需要找到一个清空屏幕的方法。第 83 行用print('\n' * 100)
做这个。这将打印 100 个换行符,导致先前打印的内容向上滚动并超出视图。这防止第二个玩家意外地看到只打算给第一个玩家的东西。虽然第二个玩家总是可以向上滚动来查看这段文字,但是对于坐在他们旁边的第一个玩家来说,这是显而易见的。
在第 114、130 和 142 行,垂直线的间距可能看起来不正确,但是程序用字符串'RED '
(末尾有一个空格)或'GOLD'
替换了花括号。这些字符串中的四个字符将导致框的其余垂直线与 ASCII 艺术画图像的其余部分对齐。
"""Carrot in a Box, by Al Sweigart email@protected
A silly bluffing game between two human players. Based on the game
from the show, 8 Out of 10 Cats.
This code is available at https://nostarch.com/big-book-small-python-programming
Tags: large, beginner, game, two-player"""
import random
print('''Carrot in a Box, by Al Sweigart email@protected
This is a bluffing game for two human players. Each player has a box.
One box has a carrot in it. To win, you must have the box with the
carrot in it.
This is a very simple and silly game.
The first player looks into their box (the second player must close
their eyes during this.) The first player then says "There is a carrot
in my box" or "There is not a carrot in my box". The second player then
gets to decide if they want to swap boxes or not.
''')
input('Press Enter to begin...')
p1Name = input('Human player 1, enter your name: ')
p2Name = input('Human player 2, enter your name: ')
playerNames = p1Name[:11].center(11) + ' ' + p2Name[:11].center(11)
print('''HERE ARE TWO BOXES:
__________ __________
/ /| / /|
+---------+ | +---------+ |
| RED | | | GOLD | |
| BOX | / | BOX | /
+---------+/ +---------+/''')
print()
print(playerNames)
print()
print(p1Name + ', you have a RED box in front of you.')
print(p2Name + ', you have a GOLD box in front of you.')
print()
print(p1Name + ', you will get to look into your box.')
print(p2Name.upper() + ', close your eyes and don\'t look!!!')
input('When ' + p2Name + ' has closed their eyes, press Enter...')
print()
print(p1Name + ' here is the inside of your box:')
if random.randint(1, 2) == 1:
carrotInFirstBox = True
else:
carrotInFirstBox = False
if carrotInFirstBox:
print('''
___VV____
| VV |
| VV |
|___||____| __________
/ || /| / /|
+---------+ | +---------+ |
| RED | | | GOLD | |
| BOX | / | BOX | /
+---------+/ +---------+/
(carrot!)''')
print(playerNames)
else:
print('''
_________
| |
| |
|_________| __________
/ /| / /|
+---------+ | +---------+ |
| RED | | | GOLD | |
| BOX | / | BOX | /
+---------+/ +---------+/
(no carrot!)''')
print(playerNames)
input('Press Enter to continue...')
print('\n' * 100) # Clear the screen by printing several newlines.
print(p1Name + ', tell ' + p2Name + ' to open their eyes.')
input('Press Enter to continue...')
print()
print(p1Name + ', say one of the following sentences to ' + p2Name + '.')
print(' 1) There is a carrot in my box.')
print(' 2) There is not a carrot in my box.')
print()
input('Then press Enter to continue...')
print()
print(p2Name + ', do you want to swap boxes with ' + p1Name + '? YES/NO')
while True:
response = input('> ').upper()
if not (response.startswith('Y') or response.startswith('N')):
print(p2Name + ', please enter "YES" or "NO".')
else:
break
firstBox = 'RED ' # Note the space after the "D".
secondBox = 'GOLD'
if response.startswith('Y'):
carrotInFirstBox = not carrotInFirstBox
firstBox, secondBox = secondBox, firstBox
print('''HERE ARE THE TWO BOXES:
__________ __________
/ /| / /|
+---------+ | +---------+ |
| {} | | | {} | |
| BOX | / | BOX | /
+---------+/ +---------+/'''.format(firstBox, secondBox))
print(playerNames)
input('Press Enter to reveal the winner...')
print()
if carrotInFirstBox:
print('''
___VV____ _________
| VV | | |
| VV | | |
|___||____| |_________|
/ || /| / /|
+---------+ | +---------+ |
| {} | | | {} | |
| BOX | / | BOX | /
+---------+/ +---------+/'''.format(firstBox, secondBox))
else:
print('''
_________ ___VV____
| | | VV |
| | | VV |
|_________| |___||____|
/ /| / || /|
+---------+ | +---------+ |
| {} | | | {} | |
| BOX | / | BOX | /
+---------+/ +---------+/'''.format(firstBox, secondBox))
print(playerNames)
# This modification made possible through the 'carrotInFirstBox variable
if carrotInFirstBox:
print(p1Name + ' is the winner!')
else:
print(p2Name + ' is the winner!')
print('Thanks for playing!')
在输入源代码并运行几次之后,尝试对其进行实验性的修改。你也可以自己想办法做到以下几点:
- 把盒子和胡萝卜的 ASCII 艺术画改成更华丽的。
- 加一句“还想再玩一次吗?”让玩家在保持得分的同时再次游戏的功能。
- 添加第三名玩家,第二名玩家必须向其诈牌。
探索程序
试着找出下列问题的答案。尝试对代码进行一些修改,然后重新运行程序,看看这些修改有什么影响。
- 注意,第 26 行有代码
p1Name[:11]
和p2Name[:11]
。请输入超过 11 个字母的名称。你注意到程序是如何显示这个名字的吗? - 如果省略第 103 行
firstBox = 'RED '
末尾的空格会怎么样? - 如果删除或注释掉第 83 行的
print('\n' * 100)
会怎么样? - 如果删除或注释掉第 100 行的
else:
和第 101 行的break
,会发生什么?
十、乔汉
乔汉是日本封建时代赌场里玩的一种骰子游戏。两个六面骰子在一个杯子里滚动,赌客必须猜和是偶数(乔)还是奇数(汉)。赌场从所有赢款中抽取一小部分。简单的随机数生成和用于确定奇数或偶数和的基本数学使得这个项目特别适合初学者。更多关于赵晗的信息可以在en.wikipedia.org/wiki/Cho-han
找到。
运行示例
当您运行chohan.py
时,输出将如下所示:
Cho-Han, by Al Sweigart email@protected
In this traditional Japanese dice game, two dice are rolled in a bamboo
cup by the dealer sitting on the floor. The player must guess if the
dice total to an even (cho) or odd (han) number.
You have 5000 mon. How much do you bet? (or QUIT)
> 400
The dealer swirls the cup and you hear the rattle of dice.
The dealer slams the cup on the floor, still covering the
dice and asks for your bet.
CHO (even) or HAN (odd)?
> cho
The dealer lifts the cup to reveal:
GO - GO
5 - 5
You won! You take 800 mon.
The house collects a 40 mon fee.
`--snip--`
工作原理
random.randint(1, 6)
调用返回一个在1
和6
之间的随机整数,非常适合表示六面骰子。但是,我们还需要显示数字一到六的日语单词。我们有一个字典,存储在JAPANESE_NUMBERS
中,将整数1
到6
映射到日语单词的字符串,而不是有一个if
语句后跟五个elif
语句。这就是第 57 行的JAPANESE_NUMBERS[dice1]
和JAPANESE_NUMBERS[dice2]
如何在一行代码中显示骰子结果的日语单词。
"""Cho-Han, by Al Sweigart email@protected
The traditional Japanese dice game of even-odd.
View this code athttps://nostarch.com/big-book-small-python-projects
Tags: short, beginner, game"""
import random, sys
JAPANESE_NUMBERS = {1: 'ICHI', 2: 'NI', 3: 'SAN',
4: 'SHI', 5: 'GO', 6: 'ROKU'}
print('''Cho-Han, by Al Sweigart email@protected
In this traditional Japanese dice game, two dice are rolled in a bamboo
cup by the dealer sitting on the floor. The player must guess if the
dice total to an even (cho) or odd (han) number.
''')
purse = 5000
while True: # Main game loop.
# Place your bet:
print('You have', purse, 'mon. How much do you bet? (or QUIT)')
while True:
pot = input('> ')
if pot.upper() == 'QUIT':
print('Thanks for playing!')
sys.exit()
elif not pot.isdecimal():
print('Please enter a number.')
elif int(pot) > purse:
print('You do not have enough to make that bet.')
else:
# This is a valid bet.
pot = int(pot) # Convert pot to an integer.
break # Exit the loop once a valid bet is placed.
# Roll the dice.
dice1 = random.randint(1, 6)
dice2 = random.randint(1, 6)
print('The dealer swirls the cup and you hear the rattle of dice.')
print('The dealer slams the cup on the floor, still covering the')
print('dice and asks for your bet.')
print()
print(' CHO (even) or HAN (odd)?')
# Let the player bet cho or han:
while True:
bet = input('> ').upper()
if bet != 'CHO' and bet != 'HAN':
print('Please enter either "CHO" or "HAN".')
continue
else:
break
# Reveal the dice results:
print('The dealer lifts the cup to reveal:')
print(' ', JAPANESE_NUMBERS[dice1], '-', JAPANESE_NUMBERS[dice2])
print(' ', dice1, '-', dice2)
# Determine if the player won:
rollIsEven = (dice1 + dice2) % 2 == 0
if rollIsEven:
correctBet = 'CHO'
else:
correctBet = 'HAN'
playerWon = bet == correctBet
# Display the bet results:
if playerWon:
print('You won! You take', pot, 'mon.')
purse = purse + pot # Add the pot from player's purse.
print('The house collects a', pot // 10, 'mon fee.')
purse = purse - (pot // 10) # The house fee is 10%.
else:
purse = purse - pot # Subtract the pot from player's purse.
print('You lost!')
# Check if the player has run out of money:
if purse == 0:
print('You have run out of money!')
print('Thanks for playing!')
sys.exit()
在输入源代码并运行几次之后,尝试对其进行实验性的修改。你也可以自己想办法做到以下几点:
- 实现这个游戏的一个变种,在维基百科的文章中有描述,多个玩家互相打赌。添加电脑控制的赌徒,他们有自己的钱包来玩。
- 为某些掷骰增加额外奖励,如 7 或蛇眼。
- 允许玩家在特定的数字上下注,以获得奖金。
探索程序
试着找出下列问题的答案。尝试对代码进行一些修改,然后重新运行程序,看看这些修改有什么影响。
- 你如何让玩家以不同的金额开始游戏?
- 该计划如何防止玩家下注超过他们的钱?
- 程序如何知道两个骰子的和是偶数还是奇数?
- 如果把第 37 行的
random.randint(1, 6)
改成random.randint(1, 1)
会怎么样? - 把 73 行(不是 74 行)的
pot // 10
改成0
,房子还收 10%的费用吗? - 如果删除或注释掉第 80、81、82 和 83 行会发生什么?