当前位置: 首页 > news >正文

刚做的婚恋网站怎么推广网站开发百灵鸟优化

刚做的婚恋网站怎么推广,网站开发百灵鸟优化,室内设计相关网站,app企业网站原文#xff1a;http://inventwithpython.com/beyond/chapter14.html 到目前为止#xff0c;这本书已经教会了你编写可读的 Python 风格代码的技巧。让我们通过查看两个命令行游戏的源代码来实践这些技术#xff1a;汉诺塔和四人一排。 这些项目很短#xff0c;并且基于文… 原文http://inventwithpython.com/beyond/chapter14.html 到目前为止这本书已经教会了你编写可读的 Python 风格代码的技巧。让我们通过查看两个命令行游戏的源代码来实践这些技术汉诺塔和四人一排。 这些项目很短并且基于文本以保持它们的范围较小但是它们展示了本书到目前为止概述的原则。我使用第 53 页“黑色不妥协的代码格式化程序”中描述的黑色工具格式化代码。我根据第 4 章的指导方针选择了变量名。我用 Python 风格风格写了代码如第 6 章所述。此外我写了注释和文档字符串如第 11 章所述。因为程序很小我们还没有涉及面向对象编程OOP,所以我写这两个项目时没有用到你将在第 15 到 17 章学到的类。 本章介绍了这两个项目的完整源代码以及代码的详细分解。这些解释不是关于代码如何工作的对 Python 语法的基本理解就是所需要的而是为什么代码是这样写的。尽管如此不同的软件开发人员对如何编写代码以及他们认为什么是python 式的有不同的看法。当然欢迎您对这些项目中的源代码提出质疑和批评。 在通读了本书中的一个项目后我建议您自己键入代码并运行几次程序以了解它们是如何工作的。然后尝试从头开始重新实现程序。您的代码不必与本章中的代码一致但是重写代码会让您了解编程所需的决策和设计权衡。 汉诺塔 汉诺塔拼图使用一叠不同大小的圆盘。圆盘的中心有孔所以你可以把它们放在三个杆子中的一个上面图 14-1。要解决这个难题玩家必须将一叠圆盘移到另一个柱子上。有三个限制 玩家一次只能移动一个盘子。玩家只能在塔顶来回移动盘子。玩家不能将较大的盘放在较小的盘上。 图 14-1一套汉诺塔的实物拼图 解决这个难题是一个常见的计算机科学问题用于讲授递归算法。我们的程序解决不了这个难题相反它会将谜题呈现给人类玩家来解决。你可以在en.wikipedia.org/wiki/Tower_of_Hanoi找到更多关于汉诺塔的信息。 输出 汉诺塔程序通过使用文本字符来表示圆盘将塔显示为 ASCII 艺术画。与现代应用相比这看起来很原始但是这种方法保持了实现的简单性因为我们只需要print()和input()调用来与用户交互。当您运行该程序时输出将如下所示。玩家输入的文本以粗体显示。 THE TOWER OF HANOI, by Al Sweigart emailprotectedMove the tower of disks, one disk at a time, to another tower. Larger disks cannot rest on top of a smaller disk.More info at https://en.wikipedia.org/wiki/Tower_of_Hanoi|| || ||emailprotected || ||emailprotected || ||emailprotected || ||emailprotected || || emailprotected || ||A B CEnter the letters of from and to towers, or QUIT. (e.g., AB to move a disk from tower A to tower B.) AC|| || |||| || ||emailprotected || ||emailprotected || ||emailprotected || || emailprotected || emailprotectedA B CEnter the letters of from and to towers, or QUIT. (e.g., AB to move a disk from tower A to tower B.)--snip--|| || |||| || emailprotected|| || emailprotected|| || emailprotected|| || emailprotected|| || emailprotectedA B CYou have solved the puzzle! Well done!对于n圆盘至少需要2 ** n - 1步才能解出汉诺塔。所以这个五盘塔需要 31 个步骤ACABCBACBABCACABCBCABACBACABCBACACBABCACBACBCABA BCACABCBACBABC最后是 AC。如果你想自己解决更大的挑战你可以把程序中的TOTAL_DISKS变量从5增加到6。 源代码 在编辑器或 IDE 中打开一个新文件并输入以下代码。保存为towerofhanoi.py。 THE TOWER OF HANOI, by Al Sweigart emailprotected A stack-moving puzzle game.import copy import sysTOTAL_DISKS 5 # More disks means a more difficult puzzle.# Start with all disks on tower A: SOLVED_TOWER list(range(TOTAL_DISKS, 0, -1))def main():Runs a single game of The Tower of Hanoi.print(THE TOWER OF HANOI, by Al Sweigart emailprotectedMove the tower of disks, one disk at a time, to another tower. Larger disks cannot rest on top of a smaller disk.More info at https://en.wikipedia.org/wiki/Tower_of_Hanoi )The towers dictionary has keys A, B, and C and valuesthat are lists representing a tower of disks. The list containsintegers representing disks of different sizes, and the start ofthe list is the bottom of the tower. For a game with 5 disks,the list [5, 4, 3, 2, 1] represents a completed tower. The blanklist [] represents a tower of no disks. The list [1, 3] has alarger disk on top of a smaller disk and is an invalidconfiguration. The list [3, 1] is allowed since smaller diskscan go on top of larger ones.towers {A: copy.copy(SOLVED_TOWER), B: [], C: []}while True: # Run a single turn on each iteration of this loop.# Display the towers and disks:displayTowers(towers)# Ask the user for a move:fromTower, toTower getPlayerMove(towers)# Move the top disk from fromTower to toTower:disk towers[fromTower].pop()towers[toTower].append(disk)# Check if the user has solved the puzzle:if SOLVED_TOWER in (towers[B], towers[C]):displayTowers(towers) # Display the towers one last time.print(You have solved the puzzle! Well done!)sys.exit()def getPlayerMove(towers):Asks the player for a move. Returns (fromTower, toTower).while True: # Keep asking player until they enter a valid move.print(Enter the letters of from and to towers, or QUIT.)print((e.g., AB to move a disk from tower A to tower B.))print()response input( ).upper().strip()if response QUIT:print(Thanks for playing!)sys.exit()# Make sure the user entered valid tower letters:if response not in (AB, AC, BA, BC, CA, CB):print(Enter one of AB, AC, BA, BC, CA, or CB.)continue # Ask player again for their move.# Use more descriptive variable names:fromTower, toTower response[0], response[1]if len(towers[fromTower]) 0:# The from tower cannot be an empty tower:print(You selected a tower with no disks.)continue # Ask player again for their move.elif len(towers[toTower]) 0:# Any disk can be moved onto an empty to tower:return fromTower, toTowerelif towers[toTower][-1] towers[fromTower][-1]:print(Cant put larger disks on top of smaller ones.)continue # Ask player again for their move.else:# This is a valid move, so return the selected towers:return fromTower, toTowerdef displayTowers(towers):Display the three towers with their disks.# Display the three towers:for level in range(TOTAL_DISKS, -1, -1):for tower in (towers[A], towers[B], towers[C]):if level len(tower):displayDisk(0) # Display the bare pole with no disk.else:displayDisk(tower[level]) # Display the disk.print()# Display the tower labels A, B, and C:emptySpace * (TOTAL_DISKS)print({0} A{0}{0} B{0}{0} C\n.format(emptySpace))def displayDisk(width):Display a disk of the given width. A width of 0 means no disk.emptySpace * (TOTAL_DISKS - width)if width 0:# Display a pole segment without a disk:print(f{emptySpace}||{emptySpace}, end)else:# Display the disk:disk * widthnumLabel str(width).rjust(2, _)print(f{emptySpace}{disk}{numLabel}{disk}{emptySpace}, end)# If this program was run (instead of imported), run the game: if __name__ __main__:main()在阅读源代码的解释之前运行这个程序玩几个游戏了解一下这个程序是做什么的。要检查错别字将其复制并粘贴到位于inventwithpython.com/beyond/diff的在线比较工具上。 编写代码 让我们仔细看看源代码看看它是如何遵循本书中描述的最佳实践和模式的。 我们将从程序的顶部开始 THE TOWER OF HANOI, by Al Sweigart emailprotected A stack-moving puzzle game.程序以多行注释开始作为towerofhanoi模块的文档字符串。内置的help()函数将使用这些信息来描述模块 import towerofhanoihelp(towerofhanoi) Help on module towerofhanoi:NAMEtowerofhanoiDESCRIPTIONTHE TOWER OF HANOI, by Al Sweigart emailprotectedA stack-moving puzzle game.FUNCTIONSdisplayDisk(width)Display a single disk of the given width. --snip--如果需要可以向模块的文档字符串中添加更多的单词甚至是信息段落。我在这里只写了一小部分因为这个程序太简单了。 模块文档字符串之后是import语句 import copy import sys布莱克将这些语句格式化为单独的语句而不是单一的语句比如import copy, sys。这使得在版本控制系统如 Git中更容易看到导入模块的添加或删除Git 跟踪程序员所做的更改。 接下来我们定义这个程序需要的常量 TOTAL_DISKS 5 # More disks means a more difficult puzzle.# Start with all disks on tower A: SOLVED_TOWER list(range(TOTAL_DISKS, 0, -1))我们在文件顶部附近定义这些变量将它们组合在一起使它们成为全局变量。我们用大写的snake_case写下了它们的名字将它们标记为常量。 TOTAL_DISKS常量表示拼图有多少个圆盘。SOLVED_TOWER变量是一个包含已求解的塔的列表的例子它包含每个盘最大的在底部最小的在顶部。我们从TOTAL_DISKS值生成这个值对于五个盘子它是[5, 4, 3, 2, 1]。 注意这个文件中没有类型提示。原因是我们可以从代码中推断出所有变量、参数和返回值的类型。例如我们已经给常量TOTAL_DISKS赋予了整数值5。由此类型检查器比如 Mypy会推断出TOTAL_DISKS应该只包含整数。 我们定义了一个main()函数程序在文件底部附近调用它 def main():Runs a single game of The Tower of Hanoi.print(THE TOWER OF HANOI, by Al Sweigart emailprotectedMove the tower of disks, one disk at a time, to another tower. Larger disks cannot rest on top of a smaller disk.More info at https://en.wikipedia.org/wiki/Tower_of_Hanoi )函数也可以有文档字符串。注意在def语句下面的main()的文档字符串。您可以通过在交互式 Shell 中运行import towerofhanoi和help(towerofhanoi.main)来查看这个文档字符串。 接下来我们编写一个注释详细描述我们用来表示塔的数据结构因为它构成了这个程序如何工作的核心 The towers dictionary has keys A, B, and C and valuesthat are lists representing a tower of disks. The list containsintegers representing disks of different sizes, and the start ofthe list is the bottom of the tower. For a game with 5 disks,the list [5, 4, 3, 2, 1] represents a completed tower. The blanklist [] represents a tower of no disks. The list [1, 3] has alarger disk on top of a smaller disk and is an invalidconfiguration. The list [3, 1] is allowed since smaller diskscan go on top of larger ones.towers {A: copy.copy(SOLVED_TOWER), B: [], C: []}我们使用SOLVED_TOWER列表作为栈这是软件开发中最简单的数据结构之一。栈是一个有序的值列表只能通过从栈顶的添加也称为推入或移除也称为弹出值来改变。这个数据结构完美地代表了我们程序中的塔。如果我们使用append()方法进行推送使用pop()方法进行弹出并且避免以任何其他方式改变列表我们可以将 Python 列表转换成栈。我们将列表的末尾视为栈的顶部。 towers列表中的每个整数代表一个特定大小的单个盘子。例如在一个有五个盘子的游戏中列表[5, 4, 3, 2, 1]将代表从底部最大的5到顶部最小的1的一整堆盘子。 请注意我们的注释还提供了有效和无效塔栈的示例。 在main()函数中我们编写了一个无限循环来运行我们的益智游戏的一个回合 while True: # Run a single turn on each iteration of this loop.# Display the towers and disks:displayTowers(towers)# Ask the user for a move:fromTower, toTower getPlayerMove(towers)# Move the top disk from fromTower to toTower:disk towers[fromTower].pop()towers[toTower].append(disk)在一个回合中玩家可以看到塔的当前状态并进入一步棋。然后程序更新towers数据结构。我们在displayTowers()和getPlayerMove()函数中隐藏了这些任务的细节。这些描述性的函数名允许main()函数提供程序功能的概述。 接下来的几行通过比较SOLVED_TOWER中的完整塔与towers[B]和towers[C]来检查玩家是否已经解决了谜题 # Check if the user has solved the puzzle:if SOLVED_TOWER in (towers[B], towers[C]):displayTowers(towers) # Display the towers one last time.print(You have solved the puzzle! Well done!)sys.exit()我们不把它与towers[A]相比因为那根柱子是从一个已经完成的塔开始的玩家需要在 B 或 C 杆上形成塔来解决这个难题。请注意我们重用了SOLVED_TOWER来制作出发塔并检查玩家是否解决了难题。因为SOLVED_TOWER是一个常量所以我们可以相信它总是拥有我们在源代码开始时赋予它的值。 我们使用的条件相当于但短于SOLVED_TOWER towers[B] or SOLVED_TOWER towers[C]这是我们在第 6 章中提到的 Python 习惯用法。如果这个条件是True玩家已经解出谜题我们结束程序。否则我们返回另一个回合。 getPlayerMove()函数要求玩家移动盘子并根据游戏规则验证移动 def getPlayerMove(towers):Asks the player for a move. Returns (fromTower, toTower).while True: # Keep asking player until they enter a valid move.print(Enter the letters of from and to towers, or QUIT.)print((e.g., AB to move a disk from tower A to tower B.))print()response input( ).upper().strip()我们开始一个无限循环继续循环直到一个return语句导致执行离开循环和函数或者一个sys.exit()调用终止程序。循环的第一部分要求玩家通过指定从和到塔的来进行移动。 注意从玩家那里接收键盘输入的input( ).upper().strip()指令。通过给出一个提示input( )调用接受玩家的文本输入。这个符号表示玩家应该输入一些东西。如果程序没有提示玩家可能会暂时认为程序冻结了。 我们对从input()返回的字符串调用upper()方法因此它返回字符串的大写形式。这允许玩家输入大写或小写的塔标签例如塔 A 的a或A然后大写字符串的strip()方法被调用返回一个两边没有任何空格的字符串以防用户在输入他们的移动时意外添加了一个空格。这种用户友好性使得我们的程序对玩家来说更容易使用。 仍然在getPlayerMove()函数中我们检查用户输入的内容 if response QUIT:print(Thanks for playing!)sys.exit()# Make sure the user entered valid tower letters:if response not in (AB, AC, BA, BC, CA, CB):print(Enter one of AB, AC, BA, BC, CA, or CB.)continue # Ask player again for their move.如果用户输入QUIT在任何情况下或者甚至在字符串的开头或结尾有空格由于对upper()和strip()的调用程序终止。我们可以让getPlayerMove()返回QUIT来指示调用者应该调用sys.exit()而不是让getPlayerMove()调用sys.exit()。但是这将使getPlayerMove()的返回值变得复杂它将返回两个字符串的元组用于玩家的移动或者单个QUIT字符串。返回单一数据类型值的函数比返回多种可能类型值的函数更容易理解。我在 177 页的“返回值应该总是有相同的数据类型”中讨论过这个问题。 在这三个塔之间只有六个往返塔组合是可能的。尽管我们在检查移动的条件中硬编码了所有六个值但是代码比类似于len(response) ! 2 or response[0] not in ABC or response[1] not in ABC or response[0] response[1]的东西更容易阅读。考虑到这些情况硬编码方法是最简单的。 一般来说将诸如AB、AC等值硬编码为幻值被认为是不好的做法这些值只有在程序有三个极点时才有效。但是尽管我们可能想通过改变TOTAL_DISKS常量来调整盘子的数量但我们不太可能在游戏中添加更多的极点。在这条线上写出每一个可能的极点移动是没问题的。 我们创建两个新变量fromTower和toTower作为数据的描述性名称。它们没有功能性目的但是它们比response[0]和response[1]更容易阅读代码 # Use more descriptive variable names:fromTower, toTower response[0], response[1]接下来我们检查选择的塔是否构成合法移动 if len(towers[fromTower]) 0:# The from tower cannot be an empty tower:print(You selected a tower with no disks.)continue # Ask player again for their move.elif len(towers[toTower]) 0:# Any disk can be moved onto an empty to tower:return fromTower, toTowerelif towers[toTower][-1] towers[fromTower][-1]:print(Cant put larger disks on top of smaller ones.)continue # Ask player again for their move.如果没有一个continue语句使执行返回到循环的开始要求玩家再次输入他们的移动。注意我们检查toTower是否为空如果是我们返回fromTower, toTower来强调移动是有效的因为你总是可以把一个盘子放在一个空的杆子上。前两个条件确保在检查第三个条件时towers[toTower]和towers[fromTower]不会为空或导致IndexError。我们对这些条件进行排序以防止IndexError或额外检查。 您的程序处理来自用户的任何无效输入或潜在的错误情况是很重要的。用户可能不知道输入什么或者他们可能会打错字。同样文件可能会意外丢失或者数据库可能会崩溃。你的程序需要对异常情况有弹性否则它们会意外崩溃或者在以后引起微妙的错误。 如果前面的条件都不为TruegetPlayerMove()返回fromTower, toTower else:# This is a valid move, so return the selected towers:return fromTower, toTower在 Python 中return语句总是返回单个值。虽然这个return语句看起来像是返回两个值但是 Python 实际上返回的是一个两个值的元组相当于return (fromTower, toTower)。Python 程序员经常在这种情况下省略括号。括号不像逗号那样定义元组。 注意程序只从main()函数调用了一次getPlayerMove()函数。这个函数并没有把我们从重复的代码中拯救出来这是使用它的最常见的目的。我们没有理由不把getPlayerMove()中的所有代码放在main()函数中。但是我们也可以使用函数来将代码组织成独立的单元这就是我们使用getPlayerMove()的方式。这样做可以防止main()函数变得太长太笨重。 displayTowers()函数在towers参数中显示塔 A、B 和 C 上的盘子 def displayTowers(towers):Display the three towers with their disks.# Display the three towers:for level in range(TOTAL_DISKS, -1, -1):for tower in (towers[A], towers[B], towers[C]):if level len(tower):displayDisk(0) # Display the bare pole with no disk.else:displayDisk(tower[level]) # Display the disk.print()它依赖于displayDisk()函数来显示塔中的每个盘子我们将在接下来介绍这个函数。for level循环检查塔的每个可能的盘子for tower循环检查塔 A、B 和 c displayTowers()函数调用displayDisk()显示每个圆盘的特定宽度如果通过0则显示没有圆盘的极点 # Display the tower labels A, B, and C:emptySpace * (TOTAL_DISKS)print({0} A{0}{0} B{0}{0} C\n.format(emptySpace))我们在屏幕上显示 A、B 和 C 标签。玩家需要这些信息来区分塔并强调塔被标记为 A、B 和 C而不是 1、2 和 3 或左、中、右。我选择不使用 1、2 和 3 作为塔标签以防止玩家将这些数字与用于表示盘子大小的数字混淆。 我们将emptySpace变量设置为每个标签之间放置的空格数这又是基于TOTAL_DISKS的因为游戏中的盘子越多两极之间的距离就越大。我们不像在print(f{emptySpace} A{emptySpace}{emptySpace} B{emptySpace}{emptySpace} C\n)中那样使用 F 字符串而是使用format()字符串方法。这允许我们在相关字符串中的任何地方使用相同的emptySpace参数产生比 F 字符串版本更短更可读的代码。 displayDisk()函数显示单个盘子及其宽度。如果没有盘子则仅显示磁极 def displayDisk(width):Display a disk of the given width. A width of 0 means no disk.emptySpace * (TOTAL_DISKS - width)if width 0:# Display a pole segment without a disk:print(f{emptySpace}||{emptySpace}, end)else:# Display the disk:disk * widthnumLabel str(width).rjust(2, _)print(f{emptySpace}{disk}{numLabel}{disk}{emptySpace}, end)我们使用前导空格、与盘子宽度相等的多个字符、两个代表宽度的字符如果宽度是一位数则包括下划线、另一系列字符以及尾随空格来表示盘子。为了只显示空的极点我们只需要前导空格、两个管道字符和尾随空格。因此我们需要用六个不同的参数对displayDisk()进行六次调用让width显示下面的塔 ||emailprotectedemailprotectedemailprotectedemailprotected emailprotected注意displayTowers()和displayDisk()函数是如何分担显示塔的责任的。虽然displayTowers()决定如何解释代表每个塔的数据结构但它依赖displayDisk()来实际显示塔的每个盘子。像这样把你的程序分成更小的函数使得每个部分更容易测试。如果程序显示盘子不正确问题可能出在displayDisk()。如果盘子顺序错误问题可能出在displayTowers()。无论哪种方式您必须调试的代码部分都会小得多。 为了调用main()函数我们使用一个常见的 Python 习惯用法 # If this program was run (instead of imported), run the game: if __name__ __main__:main()如果玩家直接运行towerofhanoi.py程序Python 会自动将__name__变量设置为__main__。但是如果有人使用import towerofhanoi将程序作为模块导入那么__name__将被设置为towerofhanoi。如果有人运行我们的程序那么if __name__ __main__:行将调用main()函数开始一个汉诺塔游戏。但是如果我们只是想将程序作为一个模块导入这样我们就可以调用其中的单个函数进行单元测试这个条件将是False而main()将不会被调用。 四人一排 四人一排是一种两人玩的丢瓦片游戏。每个玩家试图创建一排四个他们的瓷砖无论是水平的垂直的还是对角的。这类似于棋盘游戏连接四个和四个向上。该游戏使用一个7×6的直立棋盘瓷砖掉落到一列中最低的未被占据的空间。在我们的四人一排游戏中两个人类玩家 X 和 O 将相互对战而不是一个人类玩家与计算机对战。 输出 当您运行本章中的四行程序时输出将如下所示 Four-in-a-Row, by Al Sweigart emailprotectedTwo players take turns dropping tiles into one of seven columns, trying to make four in a row horizontally, vertically, or diagonally.1234567-------|.......||.......||.......||.......||.......||.......|------- Player X, enter 1 to 7 or QUIT:11234567-------|.......||.......||.......||.......||.......||X......|------- Player O, enter 1 to 7 or QUIT: --snip-- Player O, enter 1 to 7 or QUIT:41234567-------|.......||.......||...O...||X.OO...||X.XO...||XOXO..X|------- Player O has won!尝试找出许多微妙的策略你可以用它来获得四个瓷砖在一排同时阻止你的对手做同样的事情。 源代码 在编辑器或 IDE 中打开一个新文件输入以下代码并将其保存为fourinarow.py : Four-in-a-Row, by Al Sweigart emailprotected A tile-dropping game to get four-in-a-row, similar to Connect Four.import sys# Constants used for displaying the board: EMPTY_SPACE . # A period is easier to count than a space. PLAYER_X X PLAYER_O O# Note: Update BOARD_TEMPLATE COLUMN_LABELS if BOARD_WIDTH is changed. BOARD_WIDTH 7 BOARD_HEIGHT 6 COLUMN_LABELS (1, 2, 3, 4, 5, 6, 7) assert len(COLUMN_LABELS) BOARD_WIDTH# The template string for displaying the board: BOARD_TEMPLATE 1234567-------|{}{}{}{}{}{}{}||{}{}{}{}{}{}{}||{}{}{}{}{}{}{}||{}{}{}{}{}{}{}||{}{}{}{}{}{}{}||{}{}{}{}{}{}{}|-------def main():Runs a single game of Four-in-a-Row.print(Four-in-a-Row, by Al Sweigart emailprotectedTwo players take turns dropping tiles into one of seven columns, trying to make Four-in-a-Row horizontally, vertically, or diagonally. )# Set up a new game:gameBoard getNewBoard()playerTurn PLAYER_Xwhile True: # Run a players turn.# Display the board and get players move:displayBoard(gameBoard)playerMove getPlayerMove(playerTurn, gameBoard)gameBoard[playerMove] playerTurn# Check for a win or tie:if isWinner(playerTurn, gameBoard):displayBoard(gameBoard) # Display the board one last time.print(Player {} has won!.format(playerTurn))sys.exit()elif isFull(gameBoard):displayBoard(gameBoard) # Display the board one last time.print(There is a tie!)sys.exit()# Switch turns to other player:if playerTurn PLAYER_X:playerTurn PLAYER_Oelif playerTurn PLAYER_O:playerTurn PLAYER_Xdef getNewBoard():Returns a dictionary that represents a Four-in-a-Row board.The keys are (columnIndex, rowIndex) tuples of two integers, and thevalues are one of the X, O or . (empty space) strings.board {}for rowIndex in range(BOARD_HEIGHT):for columnIndex in range(BOARD_WIDTH):board[(columnIndex, rowIndex)] EMPTY_SPACEreturn boarddef displayBoard(board):Display the board and its tiles on the screen.# Prepare a list to pass to the format() string method for the board# template. The list holds all of the boards tiles (and empty# spaces) going left to right, top to bottom:tileChars []for rowIndex in range(BOARD_HEIGHT):for columnIndex in range(BOARD_WIDTH):tileChars.append(board[(columnIndex, rowIndex)])# Display the board:print(BOARD_TEMPLATE.format(*tileChars))def getPlayerMove(playerTile, board):Let a player select a column on the board to drop a tile into.Returns a tuple of the (column, row) that the tile falls into.while True: # Keep asking player until they enter a valid move.print(fPlayer {playerTile}, enter 1 to {BOARD_WIDTH} or QUIT:)response input( ).upper().strip()if response QUIT:print(Thanks for playing!)sys.exit()if response not in COLUMN_LABELS:print(fEnter a number from 1 to {BOARD_WIDTH}.)continue # Ask player again for their move.columnIndex int(response) - 1 # -1 for 0-based column indexes.# If the column is full, ask for a move again:if board[(columnIndex, 0)] ! EMPTY_SPACE:print(That column is full, select another one.)continue # Ask player again for their move.# Starting from the bottom, find the first empty space.for rowIndex in range(BOARD_HEIGHT - 1, -1, -1):if board[(columnIndex, rowIndex)] EMPTY_SPACE:return (columnIndex, rowIndex)def isFull(board):Returns True if the board has no empty spaces, otherwisereturns False.for rowIndex in range(BOARD_HEIGHT):for columnIndex in range(BOARD_WIDTH):if board[(columnIndex, rowIndex)] EMPTY_SPACE:return False # Found an empty space, so return False.return True # All spaces are full.def isWinner(playerTile, board):Returns True if playerTile has four tiles in a row on board,otherwise returns False.# Go through the entire board, checking for four-in-a-row:for columnIndex in range(BOARD_WIDTH - 3):for rowIndex in range(BOARD_HEIGHT):# Check for four-in-a-row going across to the right:tile1 board[(columnIndex, rowIndex)]tile2 board[(columnIndex 1, rowIndex)]tile3 board[(columnIndex 2, rowIndex)]tile4 board[(columnIndex 3, rowIndex)]if tile1 tile2 tile3 tile4 playerTile:return Truefor columnIndex in range(BOARD_WIDTH):for rowIndex in range(BOARD_HEIGHT - 3):# Check for four-in-a-row going down:tile1 board[(columnIndex, rowIndex)]tile2 board[(columnIndex, rowIndex 1)]tile3 board[(columnIndex, rowIndex 2)]tile4 board[(columnIndex, rowIndex 3)]if tile1 tile2 tile3 tile4 playerTile:return Truefor columnIndex in range(BOARD_WIDTH - 3):for rowIndex in range(BOARD_HEIGHT - 3):# Check for four-in-a-row going right-down diagonal:tile1 board[(columnIndex, rowIndex)]tile2 board[(columnIndex 1, rowIndex 1)]tile3 board[(columnIndex 2, rowIndex 2)]tile4 board[(columnIndex 3, rowIndex 3)]if tile1 tile2 tile3 tile4 playerTile:return True# Check for four-in-a-row going left-down diagonal:tile1 board[(columnIndex 3, rowIndex)]tile2 board[(columnIndex 2, rowIndex 1)]tile3 board[(columnIndex 1, rowIndex 2)]tile4 board[(columnIndex, rowIndex 3)]if tile1 tile2 tile3 tile4 playerTile:return Truereturn False# If this program was run (instead of imported), run the game: if __name__ __main__:main()在阅读源代码的解释之前运行这个程序玩几个游戏了解一下这个程序是做什么的。要检查拼写错误将其复制并粘贴到位于inventwithpython.com/beyond/diff的在线差异工具上。 编写代码 让我们看看程序的源代码就像我们对汉诺塔程序所做的那样。我再一次用黑色格式化了这段代码每行限制为 75 个字符。 我们将从程序的顶部开始 Four-in-a-Row, by Al Sweigart emailprotected A tile-dropping game to get four-in-a-row, similar to Connect Four.import sys# Constants used for displaying the board: EMPTY_SPACE . # A period is easier to count than a space. PLAYER_X X PLAYER_O O我们用一个文档字符串、模块导入和常量赋值开始程序就像我们在汉诺塔程序中所做的那样。我们定义了PLAYER_X和PLAYER_O常量这样我们就不必在整个程序中使用字符串X和O使得错误更容易被捕获。如果我们在使用常量时输入了一个错别字比如PLAYER_XXPython 会抛出NameError立即指出问题所在。但是如果我们用X字符打了个错别字比如XX或Z产生的 bug 可能不会立即显现出来。正如在第 71 页的“幻数”中所解释的直接使用常量而不是字符串值不仅提供了描述还为源代码中的任何拼写错误提供了预警。 当程序运行时常量不应该改变。但是程序员可以在程序的未来版本中更新它们的值。出于这个原因我们提醒程序员如果他们改变了BOARD_WIDTH的值他们应该更新BOARD_TEMPLATE和COLUMN_LABELS常量这将在后面描述 # Note: Update BOARD_TEMPLATE COLUMN_LABELS if BOARD_WIDTH is changed. BOARD_WIDTH 7 BOARD_HEIGHT 6接下来我们创建COLUMN_LABELS常量 COLUMN_LABELS (1, 2, 3, 4, 5, 6, 7) assert len(COLUMN_LABELS) BOARD_WIDTH我们稍后将使用这个常量来确保玩家选择一个有效的列。请注意如果我们将BOARD_WIDTH设置为除了7之外的值我们将不得不在COLUMN_LABELS元组中添加或删除标签。我可以通过基于代码为的BOARD_WIDTH生成COLUMN_LABELS的值来避免这种情况如下所示COLUMN_LABELS tuple([str(n) for n in range(1, BOARD_WIDTH 1)])。但是COLUMN_LABELS未来不太可能改变因为标准的四人一排游戏是在 7 乘 6 的棋盘上玩的所以我决定写出一个显式的元组值。 当然这种硬编码是一种代码味道正如第 71 页“魔术数字”中所描述的但它比其他选择更具可读性。另外assert语句警告我们在不更新COLUMN_LABELS的情况下改变BOARD_WIDTH。 和汉诺塔一样四行程序使用 ASCII 艺术画来绘制游戏棋盘。以下几行是带有多行字符串的单个赋值语句 # The template string for displaying the board: BOARD_TEMPLATE 1234567-------|{}{}{}{}{}{}{}||{}{}{}{}{}{}{}||{}{}{}{}{}{}{}||{}{}{}{}{}{}{}||{}{}{}{}{}{}{}||{}{}{}{}{}{}{}|-------这个字符串包含大括号{},format()字符串方法将用棋盘的内容替换它。displayBoard()函数稍后解释将处理这一点。因为棋盘由 7 列和 6 行组成所以我们在 6 行的每一行中使用 7 个括号对{}来代表每个插槽。注意就像COLUMN_LABELS一样我们在技术上对棋盘进行了硬编码以创建一定数量的列和行。如果我们将BOARD_WIDTH或BOARD_HEIGHT改为新的整数我们也必须更新BOARD_TEMPLATE中的多行字符串。 我们可以编写代码来基于BOARD_WIDTH和BOARD_HEIGHT常量生成BOARD_TEMPLATE就像这样 BOARD_EDGE (- * BOARD_WIDTH) BOARD_ROW | ({} * BOARD_WIDTH) |\n BOARD_TEMPLATE \n .join(COLUMN_LABELS) \n BOARD_EDGE \n (BOARD_ROW * BOARD_HEIGHT) BOARD_EDGE但是这段代码不像简单的多行字符串那样易读而且我们也不太可能改变游戏板的大小所以我们将使用简单的多行字符串。 我们开始编写main()函数它将调用我们为这个游戏制作的所有其他函数 def main():Runs a single game of Four-in-a-Row.print(Four-in-a-Row, by Al Sweigart emailprotectedTwo players take turns dropping tiles into one of seven columns, trying to make four-in-a-row horizontally, vertically, or diagonally. )# Set up a new game:gameBoard getNewBoard()playerTurn PLAYER_X我们给main()函数一个文档字符串可以通过内置的help()函数查看。main()函数还为新游戏准备游戏板并选择第一个玩家。 在main()函数内部是一个无限循环 while True: # Run a players turn.# Display the board and get players move:displayBoard(gameBoard)playerMove getPlayerMove(playerTurn, gameBoard)gameBoard[playerMove] playerTurn这个循环的每一次迭代都包括一次转弯。首先我们向玩家展示游戏板。第二玩家选择一列来放一个方块第三我们更新游戏板数据结构。 接下来我们求值玩家移动的结果 # Check for a win or tie:if isWinner(playerTurn, gameBoard):displayBoard(gameBoard) # Display the board one last time.print(Player {} has won!.format(playerTurn))sys.exit()elif isFull(gameBoard):displayBoard(gameBoard) # Display the board one last time.print(There is a tie!)sys.exit()如果玩家走了一步赢棋isWinner()返回True游戏结束。如果玩家填满了棋盘并且没有赢家isFull()返回True游戏结束。注意我们可以使用一个简单的break语句来代替调用sys.exit()。这将导致执行从while循环中中断并且因为在这个循环之后main()函数中没有代码该函数将返回到程序底部的main()调用导致程序结束。但是我选择使用sys.exit()向阅读代码的程序员表明程序将立即终止。 如果游戏还没有结束下面几行将playerTurn设置为另一个玩家 # Switch turns to other player:if playerTurn PLAYER_X:playerTurn PLAYER_Oelif playerTurn PLAYER_O:playerTurn PLAYER_X注意我本可以将elif语句变成一个简单的没有条件的else语句。但是回想一下 Python 信条之禅即显式优于隐式。这个代码明确地说如果现在轮到玩家 O那么接下来就轮到玩家 X 了。另一种说法是如果现在还没轮到参与人 X下一个就轮到参与人 X 了。尽管if和else语句自然符合布尔条件但是PLAYER_X和PLAYER_O值与True不同False : not PLAYER_X与PLAYER_O不同。因此在检查playerTurn的值时直接一点很有帮助。 或者我可以在一行程序中执行相同的操作 playerTurn {PLAYER_X: PLAYER_O, PLAYER_O: PLAYER_X}[ playerTurn]这一行使用了第 101 页“使用字典代替switch语句”中提到的字典技巧。但是像许多一行程序一样它不像直接的if和elif语句那样易读。 接下来我们定义getNewBoard()函数 def getNewBoard():Returns a dictionary that represents a Four-in-a-Row board.The keys are (columnIndex, rowIndex) tuples of two integers, and thevalues are one of the X, O or . (empty space) strings.board {}for rowIndex in range(BOARD_HEIGHT):for columnIndex in range(BOARD_WIDTH):board[(columnIndex, rowIndex)] EMPTY_SPACEreturn board这个函数返回一个字典它代表一个四行棋盘。它有用于键的(columnIndex, rowIndex)元组其中columnIndex和rowIndex是整数以及用于棋盘上每个位置的图块的X、O或.字符。我们将这些字符串分别存储在PLAYER_X、PLAYER_O和EMPTY_SPACE中。 我们的四行游戏相当简单因此使用字典来表示游戏板是一种合适的技术。尽管如此我们还是可以使用面向对象的方法来代替。我们将在第 15 到 17 章探索 OOP。 displayBoard()函数采用游戏棋盘数据结构作为board参数并使用BOARD_TEMPLATE常量在屏幕上显示棋盘 def displayBoard(board):Display the board and its tiles on the screen.# Prepare a list to pass to the format() string method for the board# template. The list holds all of the boards tiles (and empty# spaces) going left to right, top to bottom:tileChars []回想一下BOARD_TEMPLATE是一个多行字符串有几个括号对。当我们在BOARD_TEMPLATE上调用format()方法时这些括号将被传递给format()的参数替换。 tileChars变量将包含这些参数的列表。我们首先给它分配一个空白列表。tileChars中的第一个值将替换BOARD_TEMPLATE中的第一对括号第二个值将替换第二对括号依此类推。实际上我们正在创建一个来自board字典的值列表 for rowIndex in range(BOARD_HEIGHT):for columnIndex in range(BOARD_WIDTH):tileChars.append(board[(columnIndex, rowIndex)])# Display the board:print(BOARD_TEMPLATE.format(*tileChars))这些嵌套的for循环遍历棋盘上所有可能的行和列将它们添加到tileChars中的列表中。一旦这些循环结束我们就使用星号*前缀将tileChars列表中的值作为单独的参数传递给format()方法。“使用*创建变参函数”一节解释了如何使用该语法将列表中的值作为独立的函数参数代码print(*[cat, dog, rat])相当于print(cat, dog, rat)。我们需要星号因为format()方法要求每个大括号对有一个参数而不是一个列表参数。 接下来我们编写getPlayerMove()函数 def getPlayerMove(playerTile, board):Let a player select a column on the board to drop a tile into.Returns a tuple of the (column, row) that the tile falls into.while True: # Keep asking player until they enter a valid move.print(fPlayer {playerTile}, enter 1 to {BOARD_WIDTH} or QUIT:)response input( ).upper().strip()if response QUIT:print(Thanks for playing!)sys.exit()该函数以等待玩家输入有效走法的无限循环开始。这段代码类似于汉诺塔程序中的getPlayerMove()函数。请注意while循环开始时的print()调用使用了 F 字符串因此如果我们更新BOARD_WIDTH就不必更改消息。 我们检查玩家的回应是一列如果不是则continue语句将执行移回到循环的开始要求玩家进行有效的移动 if response not in COLUMN_LABELS:print(fEnter a number from 1 to {BOARD_WIDTH}.)continue # Ask player again for their move.我们可以把这个输入验证条件写成not response.isdecimal() or spam 1 or spam BOARD_WIDTH但是直接用response not in COLUMN_LABELS更简单。 接下来我们需要找出玩家所选列中的方块会落在哪一行 columnIndex int(response) - 1 # -1 for 0-based column indexes.# If the column is full, ask for a move again:if board[(columnIndex, 0)] ! EMPTY_SPACE:print(That column is full, select another one.)continue # Ask player again for their move.棋盘在屏幕上显示列标签1到7。但是板上的(columnIndex, rowIndex)索引使用基于 0 的索引所以它们的范围是从 0 到 6。为了解决这个差异我们将字符串值1到7转换为整数值0到6。 行索引从板顶部的0开始增加到板底部的6。我们检查所选列中的第一行看它是否被占用。如果是这样的话这一列就完全填满了并且continue语句将执行移回到循环的开始要求玩家进行另一次移动。 如果该列未满我们需要找到最低的未被占用的空间来放置瓷砖 # Starting from the bottom, find the first empty space.for rowIndex in range(BOARD_HEIGHT - 1, -1, -1):if board[(columnIndex, rowIndex)] EMPTY_SPACE:return (columnIndex, rowIndex)这个for循环从底部行索引、BOARD_HEIGHT - 1或6开始并向上移动直到找到第一个空白空间。然后该函数返回最低空白空间的索引。 每当棋盘满了游戏就以平局结束 def isFull(board):Returns True if the board has no empty spaces, otherwisereturns False.for rowIndex in range(BOARD_HEIGHT):for columnIndex in range(BOARD_WIDTH):if board[(columnIndex, rowIndex)] EMPTY_SPACE:return False # Found an empty space, so return False.return True # All spaces are full.isFull()函数使用一对嵌套的for循环来迭代棋盘上的每一个位置。如果它找到一个空的位置那么棋盘还没满函数返回False。如果执行通过了两个循环那么isFull()函数没有发现空的空间所以它返回True。 isWinner()函数检查玩家是否赢得游戏 def isWinner(playerTile, board):Returns True if playerTile has four tiles in a row on board,otherwise returns False.# Go through the entire board, checking for four-in-a-row:for columnIndex in range(BOARD_WIDTH - 3):for rowIndex in range(BOARD_HEIGHT):# Check for four-in-a-row going across to the right:tile1 board[(columnIndex, rowIndex)]tile2 board[(columnIndex 1, rowIndex)]tile3 board[(columnIndex 2, rowIndex)]tile4 board[(columnIndex 3, rowIndex)]if tile1 tile2 tile3 tile4 playerTile:return True如果playerTile在一行中水平、垂直或对角出现四次该函数返回True。为了判断条件是否满足我们必须检查棋盘上每组四个相邻的空格。我们将使用一系列嵌套的for循环来做到这一点。 (columnIndex, rowIndex)元组代表一个起点。我们检查起点和它右边的三个空格来寻找playerTile字符串。如果起始空格是(columnIndex, rowIndex)它右边的空格将是(columnIndex 1, rowIndex)以此类推。我们将把这四个空间中的图块保存到变量tile1、tile2、tile3和tile4中。如果所有这些变量的值都与playerTile相同我们就找到了一行四个变量并且isWinner()函数返回True。 在第 76 页的“带有数字后缀的变量”中我提到带有连续数字后缀的变量名就像这个游戏中的tile1到tile4 通常是一种代码味道表明你应该使用单个列表来代替。但是在这种情况下这些变量名是没问题的。我们不需要用列表来替换它们因为四行程序总是需要正好四个这样的tile变量。请记住代码异味不一定表明有问题这只意味着我们应该再看一眼确认我们已经用最易读的方式编写了代码。在这种情况下使用列表会使我们的代码更加复杂并且不会增加任何好处所以我们将坚持使用tile1、tile2、tile3和tile4。 我们使用类似的过程来检查垂直的四行平铺 for columnIndex in range(BOARD_WIDTH):for rowIndex in range(BOARD_HEIGHT - 3):# Check for four-in-a-row going down:tile1 board[(columnIndex, rowIndex)]tile2 board[(columnIndex, rowIndex 1)]tile3 board[(columnIndex, rowIndex 2)]tile4 board[(columnIndex, rowIndex 3)]if tile1 tile2 tile3 tile4 playerTile:return True接下来我们检查对角线模式中的四个一行的瓦片向下并向右然后我们检查对角线模式中从左到下的四个一行的图块 for columnIndex in range(BOARD_WIDTH - 3):for rowIndex in range(BOARD_HEIGHT - 3):# Check for four-in-a-row going right-down diagonal:tile1 board[(columnIndex, rowIndex)]tile2 board[(columnIndex 1, rowIndex 1)]tile3 board[(columnIndex 2, rowIndex 2)]tile4 board[(columnIndex 3, rowIndex 3)]if tile1 tile2 tile3 tile4 playerTile:return True# Check for four-in-a-row going left-down diagonal:tile1 board[(columnIndex 3, rowIndex)]tile2 board[(columnIndex 2, rowIndex 1)]tile3 board[(columnIndex 1, rowIndex 2)]tile4 board[(columnIndex, rowIndex 3)]if tile1 tile2 tile3 tile4 playerTile:return True这段代码类似于水平的四行检查所以我不会在这里重复解释。如果对四个一排的牌的所有检查都没有发现任何牌则该函数返回False以指示playerTile不是该棋盘上的赢家 return False剩下的唯一任务是调用main()函数 # If this program was run (instead of imported), run the game: if __name__ __main__:main()同样我们使用一个常见的 Python 习惯用法如果直接运行fourinarow.py则调用main(),但如果将fourinarow.py作为模块导入则不调用。 总结 汉诺塔益智游戏和四人一排游戏是简短的程序但是通过遵循本书中的实践您可以确保它们的代码可读并且易于调试。这些程序遵循几个好的实践它们被自动格式化为黑色使用文档字符串描述模块和函数并将常量放在文件顶部附近。它们将变量、函数参数和函数返回值限制为单一数据类型因此类型提示虽然是一种有益的附加文档形式但却是不必要的。 在汉诺塔中我们将这三座塔表示为一个字典包含关键字A、B和C它们的值是整数列表。这是可行的但是如果我们的程序更大或者更复杂用一个类来表示这些数据是一个好主意。本章没有用到类和 OOP 技术因为我在第 15 章到第 17 章才涉及 OOP。但是请记住为这个数据结构使用一个类是完全有效的。塔在屏幕上呈现为 ASCII 艺术画使用文本字符来显示塔的每个圆盘。 四排游戏使用 ASCII 艺术画来显示游戏板的表示。我们使用存储在BOARD_TEMPLATE常量中的多行字符串来显示它。该字符串有 42 对括号{}来显示7×6板上的每个空格。我们使用大括号所以format()字符串方法可以在那个空间用瓷砖替换它们。这样屏幕上显示的BOARD_TEMPLATE字符串如何产生游戏板就更明显了。 尽管它们的数据结构不同但这两个程序有许多相似之处。它们都在屏幕上显示它们的数据结构请求玩家输入验证输入然后在循环回到开始之前用它来更新它们的数据结构。但是我们可以用许多不同的方式编写代码来执行这些操作。使代码可读的因素最终是一种主观意见而不是对它遵守规则的程度的客观衡量。本章中的源代码表明尽管我们应该总是对任何代码异味进行第二次检查但并不是所有的代码异味都表明我们需要解决某个问题。代码可读性比盲目地遵循“零代码异味”策略更重要。
http://www.w-s-a.com/news/583306/

相关文章:

  • 网站代码建设 实例企业网站建设大概的费用
  • 制作网站软件排行榜过年做啥网站致富
  • 哪里有做网站企业seo关键词优化
  • 上海金山网站建设公司手机淘宝客网站怎么做的
  • 网站开发需要公司做网站费用计入什么科目
  • 网站优化有哪些类型免费制作app的傻瓜软件
  • 如何做网站咨询wordpress get
  • 企业网站建设网站做网站用别人的图片
  • 站长统计代码个人网站源代码
  • 求推荐专门做借条的网站公众号排版编辑器
  • 动态做网站网站开发语言查询 蔡学镛
  • 莆田网站建设创意自助建站英文
  • cms系统创建静态网站龙岗网站建设哪家好
  • 自己做的网站被封了邢台规划局网站建设
  • 网站建设项目合同wordpress主题没法用
  • 个旧市哪里有做网站wordpress内页php页面
  • 程序员接活的平台网站互联网平台建设方案
  • 网站安全建设模板深圳企业管理咨询公司
  • 做网站 还是淘宝店wordpress分类链接后加
  • wordpress腾讯云 COSseo内容优化心得
  • 特价旅游机票网站建设i营销
  • 如何成立网站深圳创业项目
  • 建设商业网站惠州网站建设推荐乐云seo
  • 如何申请免费域名做网站免费推广神器
  • 自媒体人专用网站安岳网站建设
  • 特乐网站建设做网站推广要多少钱
  • 山东省建设安全生产协会网站义乌跨境电商公司前十名
  • 做网站优化就是发文章吗起飞页自助建站平台的特点
  • 做网站还是做app好慈溪机械加工网
  • 上传下载文件网站开发的php源码腾讯企点