from datetime import datetime
class Crop:
RATE = 1 # rate of growth, units per second
def __init__(self, ..., grown=0): # allow starting growth to be set
...
self.last_update = datetime.now()
self.grown = grown
def grow(self):
"""Set current growth based on time since last update."""
now = datetime.now()
self.grown += Crop.RATE * (now - self.last_update).seconds
self.last_update = now
如果只需要知道自上次检查后作物的生长量,可以将其构建到
Crop
对象中:或者,您可以在一个单独的
^{pr2}$Growable
类中定义这个功能,并让所有增长的对象(例如Crop
,Animal
)从该超类继承grow
方法。在有不同的方法来实现这一点,这取决于你想要如何构建你的应用程序。每个游戏基本上都在运行某种循环;问题是你使用的是哪种循环。在
对于一个简单的“控制台模式”游戏,循环只是围绕
input()
的循环。。当您等待用户输入时,不会发生其他情况。这就是你要解决的问题。在解决这个问题的一个方法就是假装。在等待用户输入时,您可能无法运行任何代码,但您可以计算出您将要运行的所有代码,并执行与该代码相同的操作。如果作物应该每1.0秒生长一次,最多5次,而自从作物种植以来已经有3.7秒了,那么现在它已经长了3次了。jornsharpe的回答显示了一个很好的方法来构建这个结构。在
同样的想法也适用于由帧速率循环驱动的图形游戏,就像传统的街机游戏,但更简单。每一帧,你检查输入,更新你的所有对象,做任何输出,然后睡觉直到下一帧的时间。因为帧是以固定的速率来的,所以可以这样做:
另一种解决方案是使用后台线程。当您的main线程在等待用户输入时无法运行任何代码,但其他线程仍在运行。所以,你可以把一个background thread衍生出来。您可以使用原始的
growth
方法,使用time.sleep(1.0)
和所有内容,但是不要调用self.growth(crop)
,而是调用threading.Thread(target=self.growth, args=[crop]).start()
。这是最简单的,但这种简单是有代价的。如果每个80x25=2000块地都有一个线程,那么您将使用调度器中的所有CPU时间和线程堆栈的所有内存。因此,只有在只有几十个独立活动对象时,此选项才有效。线程的另一个问题是,必须同步多个线程上使用的任何对象,否则最终会出现竞态条件,这可能会很复杂。在“线程过多”问题(但不是同步问题)的解决方案是使用^{} 。stdlib中内置的那个并不实用(因为它为每个计时器创建一个线程),但是您可以找到第三方实现,比如^{} 。因此,与其先睡一秒钟然后再执行其余代码,不如将其余代码移到函数中,然后创建一个计时器,在一秒钟后调用该函数:
^{pr2}$现在您可以正常调用
self.growth(crop)
。但请注意,控制流是如何通过在睡眠(处于循环中间)后将所有内容移动到一个单独的函数中而实现的。在最后,您可以使用一个完整的事件循环,而不是循环输入或直到下一帧才睡觉,而是使用一个完整的事件循环:等待某个东西发生,其中该“某物”可以是用户输入,或计时器过期,或任何其他东西。这就是大多数GUI应用程序和网络服务器的工作方式,它也用于许多游戏。在事件循环程序中调度计时器事件看起来就像调度线程计时器,但没有锁。例如,对于Tkinter,它看起来像这样:
最后一个选择是将程序分成两部分:引擎和接口。将它们放在两个独立的线程(或子进程,甚至完全独立的程序)中,通过queues(或管道或套接字)进行通信,然后您可以以最自然的方式编写每个线程。这也意味着您可以用Tkinter GUI、pygame全屏图形界面甚至web应用程序来替换该界面,而无需重写引擎中的任何逻辑。在
特别是,可以将接口编写为围绕
input
的循环,它只检查输入队列等待时发生的任何更改,然后在引擎的输出队列上发布任何命令。然后将引擎写成偶数将输入队列上的新命令视为事件的t循环,或每帧检查队列的帧速率循环,或其他最有意义的循环。在相关问题 更多 >
编程相关推荐