六十六、简单替换密码
简单替换密码用一个字母代替另一个字母。由于字母A
有 26 种可能的替换,B
有 25 种可能的替换,C
有 24 种可能的替换,等等,所以可能的键的总数是26 × 25 × 24 × 23 × ... × 1
,即 403291461126605635584000000 个密钥!对于一台超级计算机来说,这对于暴力破解来说太多了,所以项目 7“凯撒破解”中使用的密码破解方法不能用于对抗简单的密码。不幸的是,狡猾的攻击者可以利用已知的弱点来破解代码。如果你想了解更多关于密码和密码破解的知识,你可以阅读我的书《Python 密码破解指南》(NoStarch 出版社,2018)。
运行示例
当您运行simplesubcipher.py
时,输出将如下所示:
Simple Substitution Cipher, by Al Sweigart
A simple substitution cipher has a one-to-one translation for each
symbol in the plaintext and each symbol in the ciphertext.
Do you want to (e)ncrypt or (d)ecrypt?
> e
Please specify the key to use.
Or enter RANDOM to have one generated for you.
> random
The key is WNOMTRCEHDXBFVSLKAGZIPYJQU. KEEP THIS SECRET!
Enter the message to encrypt.
> Meet me by the rose bushes tonight.
The encrypted message is:
Fttz ft nq zet asgt nigetg zsvhcez.
Full encrypted text copied to clipboard.
Simple Substitution Cipher, by Al Sweigart
A simple substitution cipher has a one-to-one translation for each
symbol in the plaintext and each symbol in the ciphertext.
Do you want to (e)ncrypt or (d)ecrypt?
> d
Please specify the key to use.
> WNOMTRCEHDXBFVSLKAGZIPYJQU
Enter the message to decrypt.
> Fttz ft nq zet asgt nigetg zsvhcez.
The decrypted message is:
Meet me by the rose bushes tonight.
Full decrypted text copied to clipboard.
工作原理
密钥的 26 个字母中的每一个的位置对应于字母表中相同位置的字母:
:字母表中的字母如何用一个以WNOM
开头的密钥加密。若要解密,请将底部的字母替换为上面相应的字母。
用这个密钥,字母A
加密到W
(而W
解密到A
,字母B
加密到N
,以此类推。LETTERS
和key
变量被分配给charsA
和charsB
(或者在解密时反过来)。用charsB
中的相应字符替换charsA
中的任何消息字符,以产生最终翻译的消息。
"""Simple Substitution Cipher, by Al Sweigart email@protected
A simple substitution cipher has a one-to-one translation for each
symbol in the plaintext and each symbol in the ciphertext.
More info at: https://en.wikipedia.org/wiki/Substitution_cipher
This code is available at https://nostarch.com/big-book-small-python-programming
Tags: short, cryptography, math"""
import random
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:
LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
def main():
print('''Simple Substitution Cipher, by Al Sweigart
A simple substitution cipher has a one-to-one translation for each
symbol in the plaintext and each symbol in the ciphertext.''')
# Let the user specify 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'):
myMode = 'encrypt'
break
elif response.startswith('d'):
myMode = 'decrypt'
break
print('Please enter the letter e or d.')
# Let the user specify the key to use:
while True: # Keep asking until the user enters a valid key.
print('Please specify the key to use.')
if myMode == 'encrypt':
print('Or enter RANDOM to have one generated for you.')
response = input('> ').upper()
if response == 'RANDOM':
myKey = generateRandomKey()
print('The key is {}. KEEP THIS SECRET!'.format(myKey))
break
else:
if checkKey(response):
myKey = response
break
# Let the user specify the message to encrypt/decrypt:
print('Enter the message to {}.'.format(myMode))
myMessage = input('> ')
# Perform the encryption/decryption:
if myMode == 'encrypt':
translated = encryptMessage(myMessage, myKey)
elif myMode == 'decrypt':
translated = decryptMessage(myMessage, myKey)
# Display the results:
print('The %sed message is:' % (myMode))
print(translated)
try:
pyperclip.copy(translated)
print('Full %sed text copied to clipboard.' % (myMode))
except:
pass # Do nothing if pyperclip wasn't installed.
def checkKey(key):
"""Return True if key is valid. Otherwise return False."""
keyList = list(key)
lettersList = list(LETTERS)
keyList.sort()
lettersList.sort()
if keyList != lettersList:
print('There is an error in the key or symbol set.')
return False
return True
def encryptMessage(message, key):
"""Encrypt the message using the key."""
return translateMessage(message, key, 'encrypt')
def decryptMessage(message, key):
"""Decrypt the message using the key."""
return translateMessage(message, key, 'decrypt')
def translateMessage(message, key, mode):
"""Encrypt or decrypt the message using the key."""
translated = ''
charsA = LETTERS
charsB = key
if mode == 'decrypt':
# For decrypting, we can use the same code as encrypting. We
# just need to swap where the key and LETTERS strings are used.
charsA, charsB = charsB, charsA
# Loop through each symbol in the message:
for symbol in message:
if symbol.upper() in charsA:
# Encrypt/decrypt the symbol:
symIndex = charsA.find(symbol.upper())
if symbol.isupper():
translated += charsB[symIndex].upper()
else:
translated += charsB[symIndex].lower()
else:
# The symbol is not in LETTERS, just add it unchanged.
translated += symbol
return translated
def generateRandomKey():
"""Generate and return a random encryption key."""
key = list(LETTERS) # Get a list from the LETTERS string.
random.shuffle(key) # Randomly shuffle the list.
return ''.join(key) # Get a string from the list.
# If this program was run (instead of imported), run the program:
if __name__ == '__main__':
main()
探索程序
试着找出下列问题的答案。尝试对代码进行一些修改,然后重新运行程序,看看这些修改有什么影响。
- 如果删除或注释掉第 122 行的
random.shuffle(key)
并输入密钥RANDOM
会发生什么? - 如果将第 16 行的
LETTERS
字符串扩展成'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'
会发生什么?
六十七、正弦消息
当文本向上滚动时,这个程序以波浪形显示用户选择的消息。它用实现三角正弦波函数的math.sin()
来实现这个效果。但是即使你不懂数学,这个程序也很短,很容易复制。
运行示例
当您运行sinemessage.py
时,输出将如下所示:
Sine Message, by Al Sweigart email@protected
(Press Ctrl-C to quit.)
What message do you want to display? (Max 39 chars.)
> I <3 Programming!
I <3 Programming!
I <3 Programming!
I <3 Programming!
I <3 Programming!
I <3 Programming!
I <3 Programming!
I <3 Programming!
I <3 Programming!
I <3 Programming!
I <3 Programming!
I <3 Programming!
I <3 Programming!
I <3 Programming!
I <3 Programming!
I <3 Programming!
I <3 Programming!
I <3 Programming!
I <3 Programming!
I <3 Programming!
I <3 Programming!
I <3 Programming!
I <3 Programming!
I <3 Programming!
`--snip--`
工作原理
Python 的math
模块中的math.sin()
函数接受一个参数,我们称之为x
,并返回另一个数字,称为x
的正弦值。一些数学应用使用正弦函数;在我们的程序中,它的目的仅仅是创建一个整洁的波浪效果。我们将名为step
的变量传递给math.sin()
。该变量从0
开始,在主程序循环的每次迭代中增加0.25
。
我们将使用math.sin()
的返回值来计算我们应该在用户消息的两边打印多少空格的填充。由于math.sin()
返回一个在-1.0
和1.0
之间的浮点数,但是我们想要的最小填充量是零,而不是负值,所以第 31 行在math.sin()
的返回值上加上1
,使得有效范围从0.0
到2.0
。我们当然需要不止 0 到 2 个空格,所以第 31 行将这个数字乘以一个名为multiplier
的变量来增加填充量。这个乘积就是在打印用户消息之前要添加到左侧的空格数。
结果就是你运行程序时看到的挥动的信息动画。
"""Sine Message, by Al Sweigart email@protected
Create a sine-wavy message.
This code is available at https://nostarch.com/big-book-small-python-programming
Tags: tiny, artistic"""
import math, shutil, sys, time
# Get the size of the terminal window:
WIDTH, HEIGHT = shutil.get_terminal_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
print('Sine Message, by Al Sweigart email@protected')
print('(Press Ctrl-C to quit.)')
print()
print('What message do you want to display? (Max', WIDTH // 2, 'chars.)')
while True:
message = input('> ')
if 1 <= len(message) <= (WIDTH // 2):
break
print('Message must be 1 to', WIDTH // 2, 'characters long.')
step = 0.0 # The "step" determines how far into the sine wave we are.
# Sine goes from -1.0 to 1.0, so we need to change it by a multiplier:
multiplier = (WIDTH - len(message)) / 2
try:
while True: # Main program loop.
sinOfStep = math.sin(step)
padding = ' ' * int((sinOfStep + 1) * multiplier)
print(padding + message)
time.sleep(0.1)
step += 0.25 # (!) Try changing this to 0.1 or 0.5.
except KeyboardInterrupt:
sys.exit() # When Ctrl-C is pressed, end the program.
在输入源代码并运行几次之后,尝试对其进行实验性的修改。标有(!)
的注释对你可以做的小改变有建议。
探索程序
试着找出下列问题的答案。尝试对代码进行一些修改,然后重新运行程序,看看这些修改有什么影响。
- 如果把第 30 行的
math.sin(step)
改成math.cos(step)
会怎么样? - 如果把第 30 行的
math.sin(step)
改成math.sin(0)
会怎么样?
六十八、滑动谜题
这个经典的难题依赖于一个4 × 4
的板子,有 15 个编号的瓷砖和一个自由空间。目标是滑动瓷砖,直到数字按正确的顺序排列,从左到右,从上到下。瓷砖只能滑动;不允许你直接拿起来重新排列。这个益智玩具的一些版本的特点是混乱的图像,一旦解决就形成一个完整的图片。
更多关于滑动谜题的信息可以在en.wikipedia.org/wiki/Sliding_puzzle
找到。
运行示例
当您运行slidingtilepuzzle.py
时,输出将如下所示:
Sliding Tile Puzzle, by Al Sweigart email@protected
Use the WASD keys to move the tiles
back into their original order:
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15
Press Enter to begin...
+------+------+------+------+
| | | | |
| 5 | 10 | | 11 |
| | | | |
+------+------+------+------+
| | | | |
| 6 | 3 | 7 | 2 |
| | | | |
+------+------+------+------+
| | | | |
| 14 | 1 | 15 | 8 |
| | | | |
+------+------+------+------+
| | | | |
| 9 | 13 | 4 | 12 |
| | | | |
+------+------+------+------+
(W)
Enter WASD (or QUIT): (A) ( ) (D)
> w
+------+------+------+------+
| | | | |
| 5 | 10 | 7 | 11 |
| | | | |
+------+------+------+------+
| | | | |
| 6 | 3 | | 2 |
| | | | |
+------+------+------+------+
| | | | |
| 14 | 1 | 15 | 8 |
| | | | |
+------+------+------+------+
| | | | |
| 9 | 13 | 4 | 12 |
| | | | |
+------+------+------+------+
(W)
Enter WASD (or QUIT): (A) (S) (D)
`--snip--`
工作原理
表示滑动瓦片游戏板的数据结构是列表的列表。每个内部列表代表4 × 4
棋盘的一列,并包含编号方块的字符串(或代表空白空间的BLANK
字符串)。getNewBoard()
函数返回列表的列表,所有的图块都在它们的起始位置,在右下角有一个空格。
Python 可以用类似于a, b = b, a
的语句交换两个变量的值。该程序在第 101 到 108 行使用这种技术来交换空白空间和相邻的图块,并模拟将编号的图块滑入空白空间。getNewPuzzle()
函数通过随机执行 200 次这样的交换来生成新的谜题。
"""Sliding Tile Puzzle, by Al Sweigart email@protected
Slide the numbered tiles into the correct order.
This code is available at https://nostarch.com/big-book-small-python-programming
Tags: large, game, puzzle"""
import random, sys
BLANK = ' ' # Note: This string is two spaces, not one.
def main():
print('''Sliding Tile Puzzle, by Al Sweigart email@protected
Use the WASD keys to move the tiles
back into their original order:
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 ''')
input('Press Enter to begin...')
gameBoard = getNewPuzzle()
while True:
displayBoard(gameBoard)
playerMove = askForPlayerMove(gameBoard)
makeMove(gameBoard, playerMove)
if gameBoard == getNewBoard():
print('You won!')
sys.exit()
def getNewBoard():
"""Return a list of lists that represents a new tile puzzle."""
return [['1 ', '5 ', '9 ', '13'], ['2 ', '6 ', '10', '14'],
['3 ', '7 ', '11', '15'], ['4 ', '8 ', '12', BLANK]]
def displayBoard(board):
"""Display the given board on the screen."""
labels = [board[0][0], board[1][0], board[2][0], board[3][0],
board[0][1], board[1][1], board[2][1], board[3][1],
board[0][2], board[1][2], board[2][2], board[3][2],
board[0][3], board[1][3], board[2][3], board[3][3]]
boardToDraw = """
+------+------+------+------+
| | | | |
| {} | {} | {} | {} |
| | | | |
+------+------+------+------+
| | | | |
| {} | {} | {} | {} |
| | | | |
+------+------+------+------+
| | | | |
| {} | {} | {} | {} |
| | | | |
+------+------+------+------+
| | | | |
| {} | {} | {} | {} |
| | | | |
+------+------+------+------+
""".format(*labels)
print(boardToDraw)
def findBlankSpace(board):
"""Return an (x, y) tuple of the blank space's location."""
for x in range(4):
for y in range(4):
if board[x][y] == ' ':
return (x, y)
def askForPlayerMove(board):
"""Let the player select a tile to slide."""
blankx, blanky = findBlankSpace(board)
w = 'W' if blanky != 3 else ' '
a = 'A' if blankx != 3 else ' '
s = 'S' if blanky != 0 else ' '
d = 'D' if blankx != 0 else ' '
while True:
print(' ({})'.format(w))
print('Enter WASD (or QUIT): ({}) ({}) ({})'.format(a, s, d))
response = input('> ').upper()
if response == 'QUIT':
sys.exit()
if response in (w + a + s + d).replace(' ', ''):
return response
def makeMove(board, move):
"""Carry out the given move on the given board."""
# Note: This function assumes that the move is valid.
bx, by = findBlankSpace(board)
if move == 'W':
board[bx][by], board[bx][by+1] = board[bx][by+1], board[bx][by]
elif move == 'A':
board[bx][by], board[bx+1][by] = board[bx+1][by], board[bx][by]
elif move == 'S':
board[bx][by], board[bx][by-1] = board[bx][by-1], board[bx][by]
elif move == 'D':
board[bx][by], board[bx-1][by] = board[bx-1][by], board[bx][by]
def makeRandomMove(board):
"""Perform a slide in a random direction."""
blankx, blanky = findBlankSpace(board)
validMoves = []
if blanky != 3:
validMoves.append('W')
if blankx != 3:
validMoves.append('A')
if blanky != 0:
validMoves.append('S')
if blankx != 0:
validMoves.append('D')
makeMove(board, random.choice(validMoves))
def getNewPuzzle(moves=200):
"""Get a new puzzle by making random slides from a solved state."""
board = getNewBoard()
for i in range(moves):
makeRandomMove(board)
return board
# If this program was run (instead of imported), run the game:
if __name__ == '__main__':
main()
在输入源代码并运行几次之后,尝试对其进行实验性的修改。你也可以自己想办法做到以下几点:
- 创建一个更困难的
5 × 5
变种的滑动瓷砖谜题。 - 创建一个“自动解决”模式,保存当前的瓷砖排列,然后尝试多达 40 个随机移动和停止,如果他们已经解决了难题。否则,谜题将加载保存的状态,并尝试另外 40 次随机移动。
探索程序
试着找出下列问题的答案。尝试对代码进行一些修改,然后重新运行程序,看看这些修改有什么影响。
- 如果把第 22 行的
getNewPuzzle()
改成getNewPuzzle(1)
会怎么样? - 如果把第 22 行的
getNewPuzzle()
改成getNewPuzzle(0)
会怎么样? - 如果删除或注释掉第 31 行的
sys.exit()
会发生什么?
六十九、蜗牛赛跑
你将无法承受这些比赛的快节奏刺激。。。蜗牛。但是他们在速度上的不足被 ASCII 艺术画的可爱所弥补。每只蜗牛(由贝壳的@
字符和两只眼柄的v
字符代表)缓慢但坚定地向终点线移动。多达八只蜗牛,每只都有一个自定义的名字,可以互相比赛,在它们身后留下一条黏液痕迹。这个程序适合初学者。
运行示例
当您运行snailrace.py
时,输出将如下所示:
Snail Race, by Al Sweigart email@protected
@v <-- snail
How many snails will race? Max: 8
> 3
Enter snail #1's name:
> Alice
Enter snail #2's name:
> Bob
Enter snail #3's name:
> Carol
START FINISH
| |
Alice
email@protected
Bob
email@protected
Carol
email@protected
`--snip--`
工作原理
这个程序使用两个数据结构,存储在两个变量中:snailNames
是每个蜗牛名字的字符串列表,snailProgress
是一个字典,它的关键字是蜗牛的名字,其值是表示蜗牛移动了多少个空格的整数。第 79 到 82 行读取这两个变量中的数据,在屏幕的适当位置画出蜗牛。
"""Snail Race, by Al Sweigart email@protected
Fast-paced snail racing action!
This code is available at https://nostarch.com/big-book-small-python-programming
Tags: short, artistic, beginner, game, multiplayer"""
import random, time, sys
# Set up the constants:
MAX_NUM_SNAILS = 8
MAX_NAME_LENGTH = 20
FINISH_LINE = 40 # (!) Try modifying this number.
print('''Snail Race, by Al Sweigart email@protected
@v <-- snail
''')
# Ask how many snails to race:
while True: # Keep asking until the player enters a number.
print('How many snails will race? Max:', MAX_NUM_SNAILS)
response = input('> ')
if response.isdecimal():
numSnailsRacing = int(response)
if 1 < numSnailsRacing <= MAX_NUM_SNAILS:
break
print('Enter a number between 2 and', MAX_NUM_SNAILS)
# Enter the names of each snail:
snailNames = [] # List of the string snail names.
for i in range(1, numSnailsRacing + 1):
while True: # Keep asking until the player enters a valid name.
print('Enter snail #' + str(i) + "'s name:")
name = input('> ')
if len(name) == 0:
print('Please enter a name.')
elif name in snailNames:
print('Choose a name that has not already been used.')
else:
break # The entered name is acceptable.
snailNames.append(name)
# Display each snail at the start line.
print('\n' * 40)
print('START' + (' ' * (FINISH_LINE - len('START')) + 'FINISH'))
print('|' + (' ' * (FINISH_LINE - len('|')) + '|'))
snailProgress = {}
for snailName in snailNames:
print(snailName[:MAX_NAME_LENGTH])
print('@v')
snailProgress[snailName] = 0
time.sleep(1.5) # The pause right before the race starts.
while True: # Main program loop.
# Pick random snails to move forward:
for i in range(random.randint(1, numSnailsRacing // 2)):
randomSnailName = random.choice(snailNames)
snailProgress[randomSnailName] += 1
# Check if a snail has reached the finish line:
if snailProgress[randomSnailName] == FINISH_LINE:
print(randomSnailName, 'has won!')
sys.exit()
# (!) EXPERIMENT: Add a cheat here that increases a snail's progress
# if it has your name.
time.sleep(0.5) # (!) EXPERIMENT: Try changing this value.
# (!) EXPERIMENT: What happens if you comment this line out?
print('\n' * 40)
# Display the start and finish lines:
print('START' + (' ' * (FINISH_LINE - len('START')) + 'FINISH'))
print('|' + (' ' * (FINISH_LINE - 1) + '|'))
# Display the snails (with name tags):
for snailName in snailNames:
spaces = snailProgress[snailName]
print((' ' * spaces) + snailName[:MAX_NAME_LENGTH])
print(('.' * snailProgress[snailName]) + '@v')
在输入源代码并运行几次之后,尝试对其进行实验性的修改。标有(!)
的注释对你可以做的小改变有建议。你也可以自己想办法做到以下几点:
- 添加一个随机的“速度提升”,让蜗牛向前推进四格,而不是一格。
- 增加一个蜗牛在比赛过程中可以随机进入的“睡眠模式”。这种模式使它们停下来转几圈,
zzz
出现在它们旁边。 - 加上领带的支撑,以防蜗牛同时到达终点。
探索程序
试着找出下列问题的答案。尝试对代码进行一些修改,然后重新运行程序,看看这些修改有什么影响。
- 如果把第 81 行的
snailName[:MAX_NAME_LENGTH]
改成snailNames[0]
会怎么样? - 如果把第 50 行的
print('@v')
改成print('email@protected')
会怎么样?
七十、日本算盘
算盘,也称为计数框,是一种计算工具,早在电子计算器发明之前就在许多文化中使用。图 70-1 显示了日本的算盘,叫做 soroban。每根线代表一个位置数字系统中的一个位置,线上的珠子代表该位置的数字。例如,一个 Soroban 在最右边的线上移动了两个珠子,在第二个最右边的线上移动了三个珠子,这将表示数字 32。这个程序模拟了一个 Soroban。(讽刺的是,用一台计算机来模拟一个计算机前的计算工具对我来说并非没有意义。)
soroban
soroban 中的每一列代表一个不同的数字。最右边的列是个位,左边的列是个位,左边的列是个位,以此类推。键盘顶部的Q
、W
、E
、R
、T
、Y
、U
、I
、O
和P
键可以增加它们各自位置的数字,而A
、S
、D
、F
、G
、H
、J
、K
、L
和;
按键会减少它们。虚拟 soroban 上的珠子会滑动以反映当前的数字。也可以直接输入数字。
水平分隔线下面的四个珠子是“地”珠子,将它们举到分隔线上,该数字计为 1。水平分隔线上方的珠子是一个“天”珠子,对着分隔线向下拉动它会对该数字计数为 5,因此在十位栏中向下拉动一个天堂珠子并向上拉动三个地球珠子代表数字 80。更多关于算盘以及如何使用它们的信息可以在/en.wikipedia.org/wiki/Abacus
找到。
运行示例
当您运行soroban.py
时,输出将如下所示:
Soroban - The Japanese Abacus
By Al Sweigart email@protected
+================================+
I O O O O O O O O O O I
I | | | | | | | | | | I
I | | | | | | | | | | I
+================================+
I | | | | | | | | | | I
I | | | | | | | | | | I
I O O O O O O O O O O I
I O O O O O O O O O O I
I O O O O O O O O O O I
I O O O O O O O O O O I
+==0==0==0==0==0==0==0==0==0==0==+
+q w e r t y u i o p
-a s d f g h j k l ;
(Enter a number, "quit", or a stream of up/down letters.)
> pppiiiii
+================================+
I O O O O O O O | O O I
I | | | | | | | | | | I
I | | | | | | | O | | I
+================================+
I | | | | | | | | | O I
I | | | | | | | | | O I
I O O O O O O O O O O I
I O O O O O O O O O | I
I O O O O O O O O O | I
I O O O O O O O O O O I
+==0==0==0==0==0==0==0==5==0==3==+
+q w e r t y u i o p
-a s d f g h j k l ;
(Enter a number, "quit", or a stream of up/down letters.)
`--snip--`
工作原理
displayAbacus()
函数接受一个number
参数,该参数用于计算应该在算盘上的什么位置呈现珠子。soroban 总是正好有 80 个可能的位置来放置'O'
珠子或'|'
杆段,如第 127 到 139 行多行字符串中的花括号({}
)所示。另外 10 个花括号代表number
参数的数字。
我们需要创建一个字符串列表来填充这些花括号,从左到右,从上到下。displayAbacus()
中的代码将用一个True
值填充一个hasBead
列表以显示一个'O'
珠子,用一个False
值显示一个'|'
。该列表中的前 10 个值是针对顶部“天堂”行的。如果列的数字是 0、1、2、3 或 4,我们将在该行中放置一个珠子,因为除非该列的数字是 0 到 4,否则天堂珠子不会在该行中。对于剩余的行,我们将布尔值添加到hasBead
。
第 118 到 123 行使用hasBead
创建一个包含实际的'O'
和'|'
字符串的abacusChar
列表。当与第 126 行的numberList
结合时,程序形成一个chars
列表,填充 soroban 的多行字符串 ASCII 艺术画的花括号({}
)。
"""Soroban Japanese Abacus, by Al Sweigart email@protected
A simulation of a Japanese abacus calculator tool.
More info at: https://en.wikipedia.org/wiki/Soroban
This code is available at https://nostarch.com/big-book-small-python-programming
Tags: large, artistic, math, simulation"""
NUMBER_OF_DIGITS = 10
def main():
print('Soroban - The Japanese Abacus')
print('By Al Sweigart email@protected')
print()
abacusNumber = 0 # This is the number represented on the abacus.
while True: # Main program loop.
displayAbacus(abacusNumber)
displayControls()
commands = input('> ')
if commands == 'quit':
# Quit the program:
break
elif commands.isdecimal():
# Set the abacus number:
abacusNumber = int(commands)
else:
# Handle increment/decrement commands:
for letter in commands:
if letter == 'q':
abacusNumber += 1000000000
elif letter == 'a':
abacusNumber -= 1000000000
elif letter == 'w':
abacusNumber += 100000000
elif letter == 's':
abacusNumber -= 100000000
elif letter == 'e':
abacusNumber += 10000000
elif letter == 'd':
abacusNumber -= 10000000
elif letter == 'r':
abacusNumber += 1000000
elif letter == 'f':
abacusNumber -= 1000000
elif letter == 't':
abacusNumber += 100000
elif letter == 'g':
abacusNumber -= 100000
elif letter == 'y':
abacusNumber += 10000
elif letter == 'h':
abacusNumber -= 10000
elif letter == 'u':
abacusNumber += 1000
elif letter == 'j':
abacusNumber -= 1000
elif letter == 'i':
abacusNumber += 100
elif letter == 'k':
abacusNumber -= 100
elif letter == 'o':
abacusNumber += 10
elif letter == 'l':
abacusNumber -= 10
elif letter == 'p':
abacusNumber += 1
elif letter == ';':
abacusNumber -= 1
# The abacus can't show negative numbers:
if abacusNumber < 0:
abacusNumber = 0 # Change any negative numbers to 0.
# The abacus can't show numbers larger than 9999999999:
if abacusNumber > 9999999999:
abacusNumber = 9999999999
def displayAbacus(number):
numberList = list(str(number).zfill(NUMBER_OF_DIGITS))
hasBead = [] # Contains a True or False for each bead position.
# Top heaven row has a bead for digits 0, 1, 2, 3, and 4.
for i in range(NUMBER_OF_DIGITS):
hasBead.append(numberList[i] in '01234')
# Bottom heaven row has a bead for digits 5, 6, 7, 8, and 9.
for i in range(NUMBER_OF_DIGITS):
hasBead.append(numberList[i] in '56789')
# 1st (topmost) earth row has a bead for all digits except 0.
for i in range(NUMBER_OF_DIGITS):
hasBead.append(numberList[i] in '12346789')
# 2nd earth row has a bead for digits 2, 3, 4, 7, 8, and 9.
for i in range(NUMBER_OF_DIGITS):
hasBead.append(numberList[i] in '234789')
# 3rd earth row has a bead for digits 0, 3, 4, 5, 8, and 9.
for i in range(NUMBER_OF_DIGITS):
hasBead.append(numberList[i] in '034589')
# 4th earth row has a bead for digits 0, 1, 2, 4, 5, 6, and 9.
for i in range(NUMBER_OF_DIGITS):
hasBead.append(numberList[i] in '014569')
# 5th earth row has a bead for digits 0, 1, 2, 5, 6, and 7.
for i in range(NUMBER_OF_DIGITS):
hasBead.append(numberList[i] in '012567')
# 6th earth row has a bead for digits 0, 1, 2, 3, 5, 6, 7, and 8.
for i in range(NUMBER_OF_DIGITS):
hasBead.append(numberList[i] in '01235678')
# Convert these True or False values into O or | characters.
abacusChar = []
for i, beadPresent in enumerate(hasBead):
if beadPresent:
abacusChar.append('O')
else:
abacusChar.append('|')
# Draw the abacus with the O/| characters.
chars = abacusChar + numberList
print("""
+================================+
I {} {} {} {} {} {} {} {} {} {} I
I | | | | | | | | | | I
I {} {} {} {} {} {} {} {} {} {} I
+================================+
I {} {} {} {} {} {} {} {} {} {} I
I {} {} {} {} {} {} {} {} {} {} I
I {} {} {} {} {} {} {} {} {} {} I
I {} {} {} {} {} {} {} {} {} {} I
I {} {} {} {} {} {} {} {} {} {} I
I {} {} {} {} {} {} {} {} {} {} I
+=={}=={}=={}=={}=={}=={}=={}=={}=={}=={}==+""".format(*chars))
def displayControls():
print(' +q w e r t y u i o p')
print(' -a s d f g h j k l ;')
print('(Enter a number, "quit", or a stream of up/down letters.)')
if __name__ == '__main__':
main()
探索程序
试着找出下列问题的答案。尝试对代码进行一些修改,然后重新运行程序,看看这些修改有什么影响。
- 如果把第 15 行的
abacusNumber = 0
改成abacusNumber = 9999
会怎么样? - 如果把 121 行的
abacusChar.append('O')
改成abacusChar.append('@')
会怎么样?