是时候在《我的世界》的世界里测试你的新编程知识了。我们将为我们的第一只乌龟编写程序,让它砍倒一棵树的所有木块。在这些乌龟的帮助下,你的木材供应问题将会结束!
在《我的世界》用手砍树有很多问题。它很慢,会磨损你的工具,你需要到达最顶端的木块才能完全砍倒一棵树。相比之下,海龟可以一次砍下一块木头,它们的工具不会磨损,而且它们可以悬停在你需要的高度,如图 6-1 所示。
图 6-1:四只乌龟砍一棵高大的丛林树
在我们写砍树程序之前,你需要学习一些额外的 turtle 函数,你需要考虑这个程序是如何工作的。
要砍树,你需要给乌龟配备一个全新的钻石工具。你可以用钻石镐、铲子、斧头、锄头或剑来装备海龟,但是铁制工具或用过的钻石工具都不行。幸运的是,一旦海龟装备了工具,它的耐用性永远不会降低。
要给海龟配备工具,将工具放入海龟当前选择的库存槽,或当前槽。这是库存槽,四周有粗边框。制作一把钻石鹤嘴锄,并把它放入乌龟当前的槽中。通过输入以下命令运行 Lua shell:
> lua
Interactive Lua prompt.
Call exit() to exit.然后,通过运行以下命令,用所选择的物品装备你的乌龟:
lua> turtle.equipLeft()海龟最多可以装备两个工具:一个在它们的左侧,另一个在它们的右侧。如果你想取消海龟的装备,只需调用turtle.equipLeft()或turtle.equipRight()函数,在当前选择的槽中什么都不用。海龟会把工具拿走,放到它的库存里。
海龟可以装备任何钻石工具,但钻石鹤嘴锄是最通用的。钻石铲可以挖掘泥土块,钻石斧可以挖掘木头块,但不能挖掘石头或矿石块。钻石鹤嘴锄可以挖掘所有类型的石块,所以我们将在这本书里用它来挖掘所有的海龟。有了鹤嘴锄,海龟就可以调用turtle.dig()函数,我将在下一节解释这个函数,来挖矿或者砍柴。
在我们写代码之前,让我们彻底想想伐木工海龟需要做什么。通过提前计划,你可以在早期发现程序中的错误,而不是在你写完程序后才发现。正如老木匠所说,“量两次;割一次。”我们将计划海龟的砍树算法。算法是计算机解决问题的一系列步骤。
要砍树,我们从底部的乌龟开始,挖,向前,挖到乌龟上方,向上,然后对整棵树重复最后两步。当乌龟吃完后,它会回到地上,这样就可以被捡起来了。图 6-2 至 6-6 展示了整个过程。
图 6-2:乌龟从树底开始,面向底部木块。
图 6-3:乌龟砍下底部的木块,然后向前移动所以它在树下。
图 6-4:乌龟向上劈,然后向上移动一格。
图 6-5:乌龟不停地向上劈,直到它的上面再也没有木块。
图 6-6:乌龟回到地面,这样玩家可以把它捡起来。树叶会腐烂。
我们需要使用turtle.forward()和turtle.up()函数来移动乌龟。我们还将使用turtle.dig()函数,它让乌龟挖掘(即挖掘)它前面的方块,以及turtle.digUp()函数,它挖掘乌龟上面的方块。
《我的世界》的树有各种各样的高度,所以你最不想做的事情就是写一个程序,硬编码海龟可以砍倒的树的高度。硬编码意思是用有限的、固定的解决方案来写程序。如果程序员不重写代码,硬编码的程序就无法处理不同的情况。所以你不要想这样写代码:
turtle.digUp()
turtle.up()
turtle.digUp()
turtle.up()
turtle.digUp()
turtle.up()
turtle.digUp()虽然这个程序可能很容易理解,但是海龟只能砍倒四个街区高的树。如果你想让乌龟砍倒一棵不同大小的树,你就必须重写代码,这并不理想。
相反,让我们设计一个算法来砍倒任何大小的树。例如,我们可以将这些步骤用于砍树算法:
-
从树底部的地面开始,面向树。
-
把甲鱼前面的底木块剁碎。
-
移到树下。
-
在龟的上方砍木块,每砍一次就向上移动,直到上面没有木头为止。
-
向下移动,直到乌龟回到地面。
-
停下来。
这种算法允许海龟砍倒任何高度的树。现在伐木就容易了!您将在choptree程序中实现这个算法。
在命令 shell 中输入edit choptree运行文本编辑器。在文本编辑器中,输入以下代码行。记住不要键入行号,因为它们仅用于参考。
斩波树
1\. --[[Tree Chopping program by Al Sweigart
2\. Chops down the tree in front of turtle.]]
3.
4\. if not turtle.detect() then
5. error('Could not find tree!')
6\. end
7.
8\. print('Chopping tree...')
9.
10\. if not turtle.dig() then -- chop base of tree
11. error('Turtle needs a digging tool!')
12\. end
13.
14\. turtle.forward() -- move under tree
15\. while turtle.compareUp() do
16. -- keep chopping until no more wood
17. turtle.digUp()
18. turtle.up()
19\. end
20.
21\. -- move back down to ground
22\. while not turtle.detectDown() do
23. turtle.down()
24\. end
25.
26\. print('Done chopping tree.')输入所有这些指令后,保存choptree程序。
使用鹤嘴锄挖掘乌龟,并将其放入您的库存中。在《我的世界》世界中找到一棵树,将乌龟面向树的最底部木块,如图 6-7 所示。
图 6-7:将乌龟面向一棵树最底部的木块。
右键单击乌龟打开它的 GUI,确保乌龟有燃料,并且它当前的槽是空的,这样它砍的木块就可以到那里。然后运行choptree程序,看它从树上收割所有的木材。当海龟采完木头后,它会回到地面,在那里你可以把它放回你的存货中。
如果您在运行这个程序时遇到错误,请仔细地将您的代码与本书中的代码进行比较,找出任何打字错误。如果你仍然不能修复你的程序,通过运行delete choptree删除文件,然后通过运行pastebin get 8NgPXXxN choptree下载它。
让我们一行一行地回顾一下choptree程序的源代码。乌龟首先需要检查它前面是否有一棵树。
斩波树
1\. --[[Tree Chopping program by Al Sweigart
2\. Chops down the tree in front of turtle.]]
3.
4\. if not turtle.detect() then如果乌龟前面有东西,函数turtle.detect()返回true,如果没有空气,函数false返回。如果乌龟前面有水或熔岩,这个函数也会返回false,因为乌龟可以穿过这些类型的障碍物。
就像 ComputerCraft 中有turtle.digUp()和turtle.digDown()函数一样,turtle.detectUp()和turtle.detectDown()函数可以分别检测海龟上方和下方的方块。虽然turtle.detect()功能可以检测出海龟面前是否有实心块,但不能检测出是哪种块。该程序依赖于用户在树前正确地设置乌龟。
第 4 行turtle.detect()前面的not是布尔运算符,我们接下来会学习。
not布尔运算符只对一个布尔值进行运算,并通过计算一个表达式的相反值来工作。所以not true是false,not false是true。在 Lua shell 中输入以下内容:
lua> not true
false
lua> not false
true
lua> myAge = 8
lua> not (myAge > 5)
false您可以对布尔值(如not false)或表达式(如not (myAge > 5))使用not运算符,计算如下:
在choptree程序的第 4 行,not操作符将turtle.detect()返回的布尔值改为相反的值。当乌龟前面没有障碍物时,turtle.detect()将返回false,并且not操作员将此评估为true。行if not turtle.detect() then可以理解为“如果 turtle 没有检测到阻塞,那么运行代码。”如果在 turtle 前面检测到任何代码块,执行将进入if语句后面的代码块。
还有另外两个布尔运算符,and和or。我们不会在choptree程序中使用它们,但是让我们看看它们是如何工作的,因为它们在其他程序中会很有用。
and布尔运算符比较两个布尔值,如果它们都是*true,则计算为true。如果任一值为false,则整个表达式的计算结果为false。在 Lua shell 中输入以下内容:*
lua> true and true
true
lua> true and false
false
lua> false and true
false
lua> false and false
false值的顺序不影响表达式的计算结果。
您还可以将and用于更复杂的表达式,例如:
lua> 4 < 5 and 5 < 6
true
lua> myName = 'Al'
lua> theirName = 'Alice'
lua> myName == 'Al' and theirName == 'Bob'
false表达式4 < 5 and 5 < 6是这样计算的:
因为and运算符的两边都是true,所以表达式的计算结果是true。
但是在myName == 'Al' and theirName == 'Bob'中,双方都不评价到true:
or布尔运算符比较两个布尔值,如果或为true,则求值为true。如果的两个值都是false,则整个表达式的计算结果为false。在 Lua shell 中输入以下内容:
lua> true or true
true
lua> true or false
true
lua> false or true
true
lua> false or false
false与and运算符不同,使用or运算符时,只有最后一个表达式的计算结果为false。
你也可以把or用于更复杂的表达,比如:
lua> 10 > 5 or 'Hello' == 'Hello'
true
lua> myName = 'Al'
lua> myAge = 8
lua> myName == 'Zophie' or myAge < 10
true
lua> myName == 'Zophie' or myAge ~= 8
false表达式myName == 'Zophie' or myAge < 10是这样计算的:
即使myName == 'Zophie' or myAge < 10的一边是false,另一边是true,所以整个表达式的计算结果是true。然而,myName == 'Zophie' or myAge ~= 8的双方评价到false:
这就是为什么myName == 'Zophie' or myAge ~= 8计算为false。
像and操作符一样,or操作符将处理使用不同数据类型的表达式。
布尔运算符and、or和not让您为if、elseif和while语句创建更复杂的条件。
让我们回到choptree程序。当choptree程序运行时,如果乌龟前面什么都没有——也就是说,如果第 4 行的not turtle.detect()是true——程序应该以一个错误信息终止。通常,程序只有在到达代码末尾时才会终止。但是,您可以用一个字符串参数调用error()函数来提前终止程序并显示一条错误消息。当您调用error()函数而没有传递字符串参数时,程序将简单地停止而不显示错误消息。
如果海龟没有面对障碍物,我们想要显示一条错误消息,所以我们使用error()并在第 5 行传递一个字符串参数:
斩波树
4\. if not turtle.detect() then
5. error('Could not find tree!')
6\. end如果乌龟前面没有方块,那么程序停止并在乌龟的 GUI 上打印choptree:5: Could not find tree!。Lua 添加了choptree:5部分,说明choptree程序的第 5 行有一个错误。因此,如果乌龟面前没有木块可以砍,程序就会停止。
如果第 4 行的not turtle.detect()条件是false,也就是说,如果乌龟确实检测到了它前面有东西,那么error()就不会被调用。相反,choptree程序将使用turtle.dig()砍下树的底部,然后将乌龟移到树下。木块将出现在当前的插槽中(或者另一个插槽,如果当前插槽中已经有项目)。第 8 行到第 14 行打印一条消息,告诉用户程序已经启动,将乌龟剁碎,然后将乌龟移到树下。
斩波树
8\. print('Chopping tree...')
9.
10\. if not turtle.dig() then -- chop base of tree
11. error('Turtle needs a digging tool!')
12\. end
13.
14\. turtle.forward() -- move under tree注意,乌龟必须有一个鹤嘴锄,否则turtle.dig()不起作用,会返回false。如果发生这种情况,乌龟就不能砍树了。当turtle.dig()函数返回false时,它使not turtle.dig()条件true,所以第 11 行将停止程序并显示错误信息choptree:11:Turtle needs a digging tool!。
除了turtle.dig()之外,turtle.digUp()和turtle.digDown()功能会分别砍乌龟上方或下方的方块。
turtle.compare()函数将海龟面对的方块与海龟当前位置的方块进行比较。如果它们相同,turtle.compare()返回true。如果它们不同,turtle.compare()返回false。turtle.compareUp()和turtle.compareDown()函数做同样的事情,除了它们分别比较海龟上方或下方的块。
因为程序的第一部分是在把乌龟移到树下之前砍最底层的木块,所以乌龟当前的槽里应该有一个木块被装入。我们需要检查海龟上方的块与其当前槽中的块是否是同一类型,这就是为什么在启动程序之前确保当前槽是空的很重要。我们用turtle.compareUp()来做这件事。函数看乌龟的上面而不是前面,只要乌龟上面有木块就返回true。
斩波树
15\. while turtle.compareUp() do
16. -- keep chopping until no more wood
17. turtle.digUp()
18. turtle.up()
19\. end从第 15 行开始的while循环使用turtle.compareUp()的返回值作为其条件。只要乌龟上方有木头,程序就会继续执行while循环中的代码。第 17 行挖到海龟上方,第 18 行把海龟往上推。只有当turtle.compareUp()返回false时循环才会停止,这发生在海龟上方没有木头的时候。
注意,在你可能需要的情况下,你可以使用turtle.compareDown()函数来比较海龟下面的方块。
当海龟上方不再有木头时,程序执行离开while循环。乌龟砍完了,就该回地面了。
斩波树
21\. -- move back down to ground
22\. while not turtle.detectDown() do
23. turtle.down()
24\. end
25.
26\. print('Done chopping tree.')在第 22 行,我们使用一个while循环来检查not turtle.detectDown()。只要乌龟没有检测到它下面的一个方块,条件就会返回trueT5,调用turtle.down()。当循环结束,海龟回到地面时,程序打印出Done chopping tree.,让玩家知道是时候挖掘海龟并将其捡起来(连同所有的碎木头)。
我们可以让这个程序更容易使用,只要把乌龟放在树前,就运行choptree程序。使用一个特殊的startup程序,我们可以让程序在乌龟被放置和 GUI 第一次打开时运行。接下来我们将创建startup程序。
在命令 shell 中输入edit startup运行文本编辑器。在文本编辑器中,输入以下代码行:
启动
shell.run('choptree')shell.run()函数将运行您传递的字符串,就像您在命令提示符下键入它一样。在这种情况下,当您第一次打开它的 GUI 时,turtle 运行startup,而startup程序运行choptree,因此您不必在命令提示符下键入choptree。如果运行了程序,函数shell.run()将返回true,如果找不到程序或者程序因error()被调用而停止,函数将返回false。
奖励活动:往下走
创建一个程序,让乌龟挖一个深洞,而不是砍树。你可以通过使用循环让海龟反复调用 turtle.digDown() 和 turtle.down() 来实现。让海龟调用 t urtle.up() 的次数和它调用 turtle.down() 的次数一样多,这样它就会返回水面。你不想让乌龟卡在洞底吧!
我们设计的伐木乌龟将会加快你收获木材的速度。如果你创建了多个伐木工海龟,并在上面加载了choptree和startup程序,你就可以让很多海龟同时伐木了。把一只乌龟放在一棵树上,然后当它在挖掘木块时,把另一只乌龟放在另一棵树上,以此类推。你马上就会有大量的木材供应!
然后你可以用这些海龟砍下的木头给你的海龟提供燃料。确保先用每块木头制作四块木板,因为木头和木板都提供 15 个燃料单位。
尽管这只伐木乌龟帮了很大的忙,但要把它放在每棵树的底部还是需要大量的手工工作。在接下来的几章中,您将学习如何重用您的代码,以及如何通过编程海龟来自动种植和收获树木,从而创建一个林场。*












