Python Counter in Text-Based Game - python

I'm making a text-based farmville clone using objects but I need to be able to control growth rate. I need some sort of counter that will run in the background of my program and determine how grown a crop is.
for example:
class Grow(object):
def growth(self, crop):
self.grown = 0
while self.grown < 5:
<every x number of seconds add one to self.grown>
I need something like time.sleep() but something that does not stop the program from running.
Thanks =D

If you only need to know how much the crop would have grown since you last checked, you can build this into your Crop objects:
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
Alternatively, you could define this functionality in a separate Growable class and have all objects that grow (e.g. Crop, Animal) inherit the grow method from that superclass.
class Growable:
def __init__(self, grown=0):
self.last_update = datetime.now()
self.grown = grown
def grow(self, rate):
"""Set current growth based on time since last update and rate."""
now = datetime.now()
self.grown += rate * (now - self.last_update).seconds
self.last_update = now
class Crop(Growable):
RATE = 1
def __init__(self, ..., grown=0):
super().__init__(grown)
...
def grow(self):
super().grow(Crop.RATE)

There are different ways to do this, which depend on how you want to structure your app. Every game is basically running some kind of loop; the question is what kind of loop you're using.
For a simple "console mode" game, the loop is just a loop around input().. While you're waiting for the user to type his input, nothing else can happen. And that's the problem you're trying to solve.
One way to get around this is to fake it. You may not be able to run any code while you're waiting for the user's input, but you can figure out all the code you would have run, and do the same thing it would have done. If the crop is supposed to grow every 1.0 seconds up to 5 times, and it's been 3.7 seconds since the crop was planted, it's now grown 3 times. jonrsharpe's answer shows a great way to structure this.
This same idea works for graphical games that are driven by a frame-rate loop, like a traditional arcade game, but even simpler. Each frame, you check for input, update all of your objects, do any output, then sleep until it's time for the next frame. Because frames come at a fixed rate, you can just do things like this:
def grow(self, rate):
self.grown += rate / FRAMES_PER_SECOND
A different solution is to use background threads. While your main thread can't run any code while it's waiting around for user input, any other threads keep running. So, you can spin off a background thread for the crop. You can use your original growth method, with the time.sleep(1.0) and everything, but instead of calling self.growth(crop), call threading.Thread(target=self.growth, args=[crop]).start(). That's about as simple as it gets—but that simplicity comes at a cost. If you have a thread for each of 80x25=2000 plots of land, you'll be using all your CPU time in the scheduler and all your memory for thread stacks. So, this option only works if you have only a few dozen independently-active objects. The other problem with threads is that you have to synchronize any objects that are used on multiple threads, or you end up with race conditions, and that can be complicated to get right.
A solution to the "too many threads" problem (but not the synchronization problem) is to use a Timer. The one built into the stdlib isn't really usable (because it creates a thread for each timer), but you can find third-party implementations that are, like timer2. So, instead of sleeping for a second and then doing the rest of your code, move the rest of your code into a function, and create a Timer that calls that function after a second:
def growth(self, crop):
self.grown = 0
def grow_callback():
with self.lock:
if self.grown < 5:
self.grown += 1
Timer(1.0, grow_callback).start()
Timer(1.0, grow_callback).start()
Now you can call self.growth(crop) normally. But notice how the flow of control has been turned inside-out by having to move everything after the sleep (which was in the middle of a loop) into a separate function.
Finally, instead of a loop around input or sleeping until the next frame, you can use a full event loop: wait until something happens, where that "something" can be user input, or a timer expiring, or anything else. This is how most GUI apps and network servers work, and it's also used in many games. Scheduling a timer event in an event loop program looks just like scheduling a threaded timer, but without the locks. For example, with Tkinter, it looks like this:
def growth(self, crop):
self.grown = 0
def grow_callback():
if self.grown < 5:
self.grown += 1
self.after(1000, function=grow_callback)
self.after(1000, function=grow_callback)
One final option is to break your program up into two parts: an engine and an interface. Put them in two separate threads (or child processes, or even entirely independent programs) that communicate over queues (or pipes or sockets), and then you can write each one the way that's most natural. This also means you can replace the interface with a Tkinter GUI, a pygame full-screen graphics interface, or even a web app without rewriting any of your logic in the engine.
In particular, you can write the interface as a loop around input that just checks the input queue for any changes that happened while it was waiting, and then posts any commands on the output queue for the engine. Then write the engine as an event loop that treats new commands on the input queue as events, or a frame-rate loop that checks the queue every frame, or whatever else makes the most sense.

Related

How to start, stop, and restart a thread?

Fairly new to Python; working on a Raspberry Pi 4 with Python 3.4.3.
Got a code working to listen for 2 distinct alarms in my lab - one for a -80 freezer getting too warm, and the other for a -20 freezer. Code listens on a microphone, streams data, Fourier-transforms it, detects the peaks I'm interested in, and triggers events when they're found - eventually going to email me and my team if an alarm goes off, but still just testing with Print commands atm. Let's call them Alarm A/EventA and Alarm B/Event B.
I want it to trigger Event A when Alarm A is detected, but then wait 1 hour before triggering Event A again (if Alarm A is still going off/goes off again in an hour).
Meanwhile, though, I also want it to continue listening for Alarm B and trigger Event B if detected - again, only once per hour.
Since I can't just do time.sleep, then, I'm trying to do it with Threads - but am having trouble starting, stopping, and restarting a thread for the 1 hour (currently just 10 second for testing purposes) delay.
I have variables CounterA and CounterB set to 0 to start. When Alarm A is detected I have the program execute EventA and up CounterA to 1; ditto for AlarmB/EventB/CounterB. EventA and EventB are only triggered if CounterA and CounterB are <1.
I'm having a real hard time resetting the counters after a time delay, though. Either I end up stalling the whole program after an event is triggered, or I get the error that threads can only be started once.
Here are the relevant sections of the code:
import time
import threading
CounterA = 0
CounterB = 0
def Aresetter():
time.sleep(10)
global CounterA
CounterA=CounterA-1
thA.join()
def Bresetter():
time.sleep(10)
global CounterB
CounterB=CounterB-1
thB.join()
thA = threading.Thread(target = Aresetter)
thB = threading.Thread(target = Bresetter)
if any(#Alarm A detection) and CounterA<1:
print('Alarm A!')
CounterA=CounterA+1
thA.start()
elif any(#Alarm B detection) and CounterB<1:
print('Alarm B!')
CounterB=CounterB+1
thB.start()
else:
pass
I think the crux of my problem is I can't have the resetter functions join the threads to main once they're finished with their delayed maths - but I also don't know how to do that in the main program without making it wait for the same amount of time and thus stalling everything...
You don't need threads for this at all.
Just keep track of the last time (time.time()) you triggered each alarm, and don't trigger them if less than 60 minutes (or whatever the threshold is) has elapsed since the last time.
Something like (semi pseudocode)...
import time
last_alarm_1 = 0 # a long time ago, so alarm can trigger immediately
# ...
if alarm_1_cond_met():
now = time.time()
if now - last_alarm_1 > 60 * 60: # seconds
send_alarm_1_mail()
last_alarm_1 = now
Repeat for alarm 2 :)
AKX has a better solution to your problem, but you should be aware of what this does when Aresetter() is called by the thA thread:
def Aresetter():
...
thA.join()
The thA.join() method doesn't do anything to the thA thread. All it does is, it waits for the thread to die, and then it returns. But, if it's the thA thread waiting for itself to die, it's going to be waiting for a very long time.
Also, there's this:
How to...restart a thread?
You can't. I don't want to explore why it makes any sense, but you just can't do that. It's not how threads work. If you want your program to do the same task more than one time "in another thread," you have a couple of options:
Create a new thread to do the task each time.
Create a single thread that does the same task again and again, possibly sleep()ing in between, or possibly awaiting some message/signal/trigger before each repetition.
Submit a task to a thread pool* each time you want the thing to be done.
Option (2) could be better than option (1) because creating and destroying threads is a lot of work. With option (2) you're only doing that once.
Option (1) could be better than option (2) because threads use a significant amount of memory. If the thread doesn't exist when it's not needed, then that memory could be used by something else.
Option (3) could be better than the both of them if the same thread pool is also used for other purposes in your program. The marginal cost of throwing a few more tasks at an already-existing thread pool is trivial.
* I don't know that Python has a ready-made, first-class ThreadPool class for you to use. It has this, https://stackoverflow.com/a/64373926/801894 , but I've never used it. It's not that hard though to create your own simple thread pool.

In python, how can I run a function without the program waiting for its completion?

In Python, I am making a cube game (like Minecraft pre-classic) that renders chunk by chunk (16x16 blocks). It only renders blocks that are not exposed (not covered on all sides). Even though this method is fast when I have little height (like 16x16x2, which is 512 blocks in total), once I make the terrain higher (like 16x16x64, which is 16384 blocks in total), rendering each chunk takes roughly 0.03 seconds, meaning that when I render multiple chunks at once the game freezes for about a quarter of a second. I want to render the chunks "asynchronously", meaning that the program will keep on drawing frames and calling the chunk render function multiple times, no matter how long it takes. Let me show you some pictures to help:
I tried to make another program in order to test it:
import threading
def run():
n=1
for i in range(10000000):
n += 1
print(n)
print("Start")
threading.Thread(target=run()).start()
print("End")
I know that creating such a lot of threads is not the best solution, but nothing else worked.
Threading, however, didn't work, as this is what the output looked like:
>>> Start
>>> 10000001
>>> End
It also took about a quarter of a second to complete, which is about how long the multiple chunk rendering takes.
Then I tried to use async:
import asyncio
async def run():
n = 1
for i in range(10000000):
n += 1
print(n)
print("Start")
asyncio.run(run())
print("End")
It did the exact same thing.
My questions are:
Can I run a function without stopping/pausing the program execution until it's complete?
Did I use the above correctly?
Yes. No. The answer is complicated.
First, your example has at least one error on it:
print("Start")
threading.Thread(target=run).start() #notice the missing parenthesis after run
print("End")
You can use multithreading for your game of course, but it can come at a disadvantage of code complexity because of synchronization and you might not gain any performance because of GIL.
asyncio is probably not for this job either, since you don't need to highly parallelize many tasks and it has the same problems with GIL as multithreading.
The usual solution for this kind of problem is to divide your work into small batches and only process the next batch if you have time to do so on the same frame, kind of like so:
def runBatch(range):
for x in range:
print(x)
batches = [range (x, x+200) for x in range(0, 10000, 200)]
while (true): # main loop
while (timeToNextFrame() > 15):
runBatch(batch.pop())
renderFrame() #or whatever
However, in this instance, optimizing the algorithm itself could be even better than any other option. One thing that Minecraft does is it subdivides chunks into subchunks (you can mostly ignore subchunks that are full of blocks). Another is that it only considers the visible surfaces of the blocks (renders only those sides of the block that could be visible, not the whole block).
asyncio only works asynchronously only when your function is waiting on I/O task like network call or wait on disk I/O etc.
for non I/O tasks to execute asynchronously multi-threading is the only option so create all your threads and wait for the threads to complete their tasks using thread join method
from threading import Thread
import time
def draw_pixels(arg):
time.sleep(arg)
print(arg)
threads = []
args = [1,2,3,4,5]
for arg in args:
t = Thread(target=draw_pixels, args=(arg, ))
t.start()
threads.append(t)
# join all threads
for t in threads:
t.join()

Using queue to synchronize Tk object

I'm trying to create a chess game using tkinter. I don't have a huge experience in python programming, but I kind of find weird the philosophy of tkinter : if my assumptions are correct, it seems to me that using tkinter means setting it as the base of the project, and everything has to work around it. And what I mean by that is that using whatever code that is not 'wrapped' in the tkinter framework is a pain to deal with (you have to use the event system, you have to use the after method if you want to perform an action after starting the main loop, etc.)
I have a rather different view on that, and in my chess project I simply consider the tkinter display as a part of my rendering system, and the event system provided by tkinter as a part of my input parser system. That being said, I want to be able to easily change the renderer or the input parser, which means that I could want to detect input from the terminal (for instance by writing D2 D3) instead of moving the objects on the screen. I could also want to print the chessboard on the terminal instead of having a GUI.
More to the point, because tkinter blocks the thread through the mainloop method instead of looping in another thread, I have to put my Tk object in a different thread, so that I can run the rest of my program in parallel. And I'm having a tough time doing it, because my Tk variable contained by my thread needs to be accessed by my program, to update it for instance.
After quite a bit of research, I found that queues in python were synchronized, which means that if I put my Tk object in a queue, I could access it without any problem from the main thread. I tried to see if the following code was working :
import threading, queue
class VariableContainer(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.queue = queue.Queue()
def run(self):
self.queue.put("test")
container = VariableContainer()
container.start()
print(container.queue.get(False))
and it does ! The output is test.
However, if I replace my test string by a Tk object, like below :
import threading, queue
import tkinter
class VariableContainer(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.queue = queue.Queue()
def run(self):
root = tkinter.Tk()
self.queue.put(root)
root.mainloop() # whether I call the mainloop or not doesn't change anything
container = VariableContainer()
container.start()
print(container.queue.get(False))
then the print throws an error, stating that the queue is empty.
(Note that the code above is not the code of my program, it is just an exemple since posting sample codes from my project might be less clear)
Why?
The answer to the trivial question you actually asked: you have a race condition because you call Queue.get(block=False). Tk taking a lot longer to initialize, the main thread almost always wins and finds the queue still empty.
The real question is “How do I isolate my logic from the structure of my interface library?”. (While I understand the desire for a simple branch point between “read from the keyboard” and “wait for a mouse event”, it is considered more composable, in the face of large numbers of event types, sources, and handlers, to have one event dispatcher provided by the implementation. It can sometimes be driven one event at a time, but that composes less well with other logic than one might think.)
The usual answer to that is to make your logic a state machine rather than an algorithm. Mechanically, this means replacing local variables with attributes on an object and dividing the code into methods on its class (e.g., one call per “read from keyboard” in a monolithic implementation). Sometimes language features like coroutines can be used to make this transformation mostly transparent/automatic, but they’re not always a good fit. For example:
def algorithm(n):
tot=0
for i in range(n):
s=int(input("#%s:"%i))
tot+=(i+1)*(n-i)*s
print(tot)
class FSM(object):
def __init__(self,n):
self.n=n
self.i=self.tot=0
def send(self,s):
self.tot+=(self.i+1)*(self.n-self.i)*s
self.i+=1
def count(self): return self.i
def done(self): return self.i>=self.n
def get(self):
return self.tot
def coroutine(n): # old-style, not "async def"
tot=0
for i in range(n):
s=(yield)
tot+=(i+1)*(n-i)*s
yield tot
Having done this, it’s trivial to layer the traditional stream-driven I/O back on top, or to connect it to an event-driven system (be it a GUI or asyncio). For example:
def fsmClient(n):
fsm=FSM(n)
while not fsm.done():
fsm.send(int(input("#%s:"%fsm.count())))
return fsm.get()
def coClient(n):
co=coroutine(n)
first=True
while True:
ret=co.send(None if first else
int(input("#%s:"%fsm.count())))
if ret is not None:
co.close()
return ret
first=False
These clients can work with any state machine/coroutine using the same interface; it should be obvious how to instead supply values from a Tkinter input box or so.

Creating threads from within a Python multiprocess

I'm writing code for a game, with the AI running as threads. They are stored in a list with the relevant information so that when their character 'dies', they can be efficiently removed from play by simply removing them from the list of active AIs.
The threads controlling them were initially called from within another thread while a tkinter window controlling the player runs in the main thread - however, the AI threads caused the main thread to slow down. After searching for a way to decrease the priority of the thread creating the AI, I found that I would have to use multiprocessing.Process instead of threading.Thread, and then altering its niceness - but when I try to do this, the AI threads don't work. The functions running this are defined within class AI():
def start():
'''
Creates a process to oversee all AI functions
'''
global activeAI, AIsentinel, done
AIsentinel=multiprocessing.Process(target=AI.tick,args=(activeAI, done))
AIsentinel.start()
and
def tick(activeAI, done):
'''
Calls all active AI functions as threads
'''
AIthreads=[]
for i in activeAI:
# i[0] is the AI function, i[1] is the character class instance (user defined)
# and the other list items are its parameters.
AIthreads.append(threading.Thread(name=i[1].name,target=lambda: i[0](i[1],i[2],i[3],i[4],i[5])))
AIthreads[-1].start()
while not done:
for x in AIthreads:
if not x.is_alive():
x.join()
AIthreads.remove(x)
for i in activeAI:
if i[1].name==x.name:
AIthreads.append(threading.Thread(name=i[1].name,target=lambda: i[0](i[1],i[2],i[3],i[4],i[5])))
AIthreads[-1].start()
The outputs of these threads should display in stdout, but when running the program, nothing appears - I assume it's because the threads don't start, but I can't tell if it's just because their output isn't displaying. I'd like a way to get this solution working, but I suspect this solution is far too ugly and not worth fixing, or simply cannot be fixed.
In the event this is as horrible as I fear, I'm also open to new approaches to the problem entirely.
I'm running this on a Windows 10 machine. Thanks in advance.
Edit:
The actual print statement is within another thread - when an AI performs an action, it adds a string describing its action to a queue, which is printed out by yet another thread (as if I didn't have enough of those). As an example:
battle_log.append('{0} dealt {1} damage to {2}!'.format(self.name,damage[0],target.name))
Which is read out by:
def battlereport():
'''
Displays battle updates.
'''
global battle_log, done
print('\n')
while not done:
for i in battle_log:
print(i)
battle_log.remove(i)

Fast and Precise Python Repeating Timer

I need to send repeating messages from a list quickly and precisely. One list needs to send the messages every 100ms, with a +/- 10ms window. I tried using the code below, but the problem is that the timer waits the 100ms, and then all the computation needs to be done, making the timer fall out of the acceptable window.
Simply decreasing the wait is a messy, and unreliable hack. The there is a Lock around the message loop in the event the list gets edited during the loop.
Thoughts on how to get python to send messages consistently around 100ms? Thanks
from threading import Timer
from threading import Lock
class RepeatingTimer(object):
def __init__(self,interval, function, *args, **kwargs):
super(RepeatingTimer, self).__init__()
self.args = args
self.kwargs = kwargs
self.function = function
self.interval = interval
self.start()
def start(self):
self.callback()
def stop(self):
self.interval = False
def callback(self):
if self.interval:
self.function(*self.args, **self.kwargs)
Timer(self.interval, self.callback, ).start()
def loop(messageList):
listLock.acquire()
for m in messageList:
writeFunction(m)
listLock.release()
MESSAGE_LIST = [] #Imagine this is populated with the messages
listLock = Lock()
rt = RepeatingTimer(0.1,loop,MESSAGE_LIST)
#Do other stuff after this
I do understand that the writeFunction will cause some delay, but not more than the 10ms allowed. I essentially need to call the function every 100ms for each message. The messagelist is small, usually less than elements.
The next challenge is to have this work with every 10ms, +/-1ms :P
Yes, the simple waiting is messy and there are better alternatives.
First off, you need a high-precision timer in Python. There are a few alternatives and depending on your OS, you might want to choose the most accurate one.
Second, you must be aware of the basics preemptive multitasking and understand that there is no high-precision sleep function, and that its actual resolution will differ from OS to OS too. For example, if we're talking Windows, the minimal sleep interval might be around 10-13 ms.
And third, remember that it's always possible to wait for a very accurate interval of time (assuming you have a high-resolution timer), but with a trade-off of high CPU load. The technique is called busy waiting:
while(True):
if time.clock() == something:
break
So, the actual solution is to create a hybrid timer. It will use the regular sleep function to wait the main bulk of the interval, and then it'll start probing the high-precision timer in the loop, while doing the sleep(0) trick. Sleep(0) will (depending on the platform) wait the least possible amount of time, releasing the rest of the remaining time slice to other processes and switching the CPU context. Here is a relevant discussion.
The idea is thoroughly described in the Ryan Geiss's Timing in Win32 article. It's in C and for Windows API, but the basic principles apply here as well.
Store the start time. Send the message. Get the end time. Calculate timeTaken=end-start. Convert to FP seconds. Sleep(0.1-timeTaken). Loop back.
try this:
#!/usr/bin/python
import time; # This is required to include time module.
from threading import Timer
def hello(start, interval, count):
ticks = time.time()
t = Timer(interval - (ticks-start-count*interval), hello, [start, interval, count+1])
t.start()
print "Number of ticks since 12:00am, January 1, 1970:", ticks, " #", count
dt = 1.25 # interval in sec
t = Timer(dt, hello, [round(time.time()), dt, 0]) # start over at full second, round only for testing here
t.start()

Categories

Resources