views:

508

answers:

7

Hi, I'm pretty new to Python, and programming in general and I'm creating a virtual pet style game for my little sister.

Is it possible to run 2 while loops parallel to each other in python? eg:

while 1:
    input_event_1 = gui.buttonbox(
        msg = 'Hello, what would you like to do with your Potato Head?',
        title = 'Main Screen',
        choices = ('Check Stats', 'Feed', 'Exercise', 'Teach', 'Play', 'Go to Doctor', 'Sleep', 'Change Favourite Thing', 'Get New Toy', 'Quit'))
    if input_event_1 == 'Check Stats':
        myPotatoHead.check_p_h_stats()
    elif input_event_1 == 'Feed':
        myPotatoHead.feed_potato_head()
    elif input_event_1 == 'Exercise':
        myPotatoHead.exercise_potato_head()
    elif input_event_1 == 'Teach':
        myPotatoHead.teach_potato_head(myPotatoHead)
    elif input_event_1 == 'Play':
        myPotatoHead.play_with_toy()
    elif input_event_1 == 'Sleep':
        myPotatoHead.put_p_h_asleep()
    elif input_event_1 == 'Go to Doctor':
        myPotatoHead.doctor_check_up()
    elif input_event_1 == 'Change Favourite Thing':
        myPotatoHead.change_favourite_thing()
    elif input_event_1 == 'Quit':
        input_quit = gui.ynbox(
            msg = 'Are you sure you want to quit?',
            title = 'Confirm quit',
            choices = ('Quit', 'Cancel'))
        if input_quit == 1:
            sys.exit(0)

while 1:
    time.sleep(20)
    myPotatoHead.hunger = str(float(myPotatoHead.hunger) + 1.0)
    myPotatoHead.happiness = str(float(myPotatoHead.happiness) - 1.0)
    myPotatoHead.tiredness = str(float(myPotatoHead.tiredness) + 1.0)

If not, is there some way that I can turn this into one loop? I want the stuff in the second loop to happen every 20 seconds, but the stuff in the first loop to by constantly happening.

Thanks for any help

A: 

i think they cannot be coupled in to one while loop. maybe you need to check the threading or multiprocessing library.

Dyno Fu
A: 

Have a look at Threading.Timer.

There is a code recipe here to schedule a function to run every 5 seconds.

import thread
import threading

class Operation(threading._Timer):
    def __init__(self, *args, **kwargs):
        threading._Timer.__init__(self, *args, **kwargs)
        self.setDaemon(True)

    def run(self):
        while True:
            self.finished.clear()
            self.finished.wait(self.interval)
            if not self.finished.isSet():
                self.function(*self.args, **self.kwargs)
            else:
                return
            self.finished.set()

class Manager(object):

    ops = []

    def add_operation(self, operation, interval, args=[], kwargs={}):
        op = Operation(interval, operation, args, kwargs)
        self.ops.append(op)
        thread.start_new_thread(op.run, ())

    def stop(self):
        for op in self.ops:
            op.cancel()
        self._event.set()

if __name__ == '__main__':
    # Print "Hello World!" every 5 seconds

    import time

    def hello():
        print "Hello World!"

    timer = Manager()
    timer.add_operation(hello, 5)

    while True:
        time.sleep(.1)
jbochi
@Those who downvoted my answer: Could you please care to explain why?The second loop actually is not necessary. All it was requested is that it should run every 20s and this can be easily achieved by modifying the code the I have posted.
jbochi
+4  A: 

The only way to "have two while loops in parallel" would be to place them on different threads, but then you need to tackle the synchronization and coordination problems between them since they're reaching into the same object.

I suggest you instead put a time check in the first (and single) loop and perform the increases that you now have in the second loop proportionately to that time-check; not quite satisfactory since the buttonbox call might take an indefinite amount of time to return, but way simpler to arrange, esp. for a beginner, than proper threading coordination.

Once you do have the basic logic in place and working, then you can consider threads again (with a periodic timer for what you'd like in the 2nd loop in one thread, the blocking buttonbox call in the main thread [[I think in easygui it has to be]], both feeding events into a Queue.Queue [[intrinsically thread-safe]] with another thread getting them and operating accordingly, i.e. most of what you now have in the 1st loop). But that's quite an advanced architectural problem, which is why I recommend you don't try to deal w/it right now!-)

Alex Martelli
+1  A: 

Essentially to have processing to happen in parallel you have several solutions

1- Separate processes (ie: programs) running independently that speak to one another through a specific protocol (eg: Sockets)

2- Or you can have the one process spawn off multiple threads

3- Build an event queue internally and process them one by one

That is the general picture.

As for the specific answer to your question, you said "the stuff in the first loop to b[e] constantly happening". The reality is you never want this to happen all the time, because all that will do is use up 100% of the CPU and nothing else will ever get done

The simplest solution is probably number 3.

The way I would implement it is in my main loop have a thread that goes through an event queue and sets a timer for each event. Once all the timers have been sent the main loop then goes to sleep.

When a timer times out, an other function will then run the corresponding function for the event that triggered that timer.

In your case, you have two events. One for displaying the selection menu (first loop) and the second for changing myPotatoHead. The timer associated with the first one, I would set to 0.5sec, making it larger reduces CPU usage but slows down responsivness, increasing it usses up more CPU, for the second event I would set a 20 second timer.

Ofcourse when the timer expires, you would not do while 1 but you will just go through your while loop body once (ie get rid of while).

hhafez
A: 

put one of them into a function, the threading.Thread class supports a target attribute:

import threading
threading.Thread(target=yourFunc).start()

Will start yourFunc() running in the background.

Richo
...and with no effort at synchronizing and coordinating multiple threads using and altering the same object, just wait for the weird, inexplicable, irreproducible anomalies due to the race conditions:-(.
Alex Martelli
To be fair, it answers the question exactly. Why is this downvoted?
Sukasa
Because of this part: "I'm pretty new to Python, and programming in general." Threads are hard. You're offering a guy who's new to programming a very error-prone and unnecessary strategy and demonstrating exactly what the bug looks like without explaining how it's broken.
Dustin
Threads are not hard, python makes them easy, especially since he's almost certainly running CPython which uses the GIL to ensure that while you can confuse yourself with what's accessing when, it can't corrupt with simultanious accessesAnd for his purposes (one pushing onto the stack, one popping off of it) it really doesn't matter. The GIL catches any concurrency issues, his code will work fine.Thanks for your concern, but I think you'll fine this is a turnkey solution to his problem.
Richo
Richo: It's my general experience that only those who aren't terribly experienced with threading think it's easy (I used to be less so). Your understanding of the GIL is clearly wrong. The presence of locking primitives in core python should be a clue. Imperative code not designed for thread safety should not be considered thread safe. Experts get this wrong every day.
Dustin
Dustin: I can see what you're getting at, but in this instance I don't believe you're correct. For his purposes (one thread adds stuff to a list, another thread pops stuff off and does stuff with it) he doesn't need to worry about thread safety as the two threads cannot interact with the same object simultaniously (excepting the list itself, and the GIL /does/ protect it's integrity AFAIK. Definately not professiong to be an expert, just someone who's written a lot of not-obviously-broken threaded code.
Richo
A: 

There is also a package called SimPy that you could also look at. The threading and multiprocessing libraries may also help.

Casey
+3  A: 

You should use State Machines for this (see the Apress pygame book - downloads here: http://apress.com/book/downloadfile/3765 ), see chapter 7.

A simplified state machine:

def do_play(pet, time_passed):
    pet.happiness += time_pass*4.0

def do_feed(pet, time_passed):
    pet.hunger -= time_passed*4.0

def do_sleep(pet, time_passed):
    pet.tiredness += time_passed*4.0
    if pet.tiredness <= 0:
        return 'Waiting'

def do_waiting(pet, time_passed):
    pass

def do_howl(pet, time_passed):
    print 'Hoooowl'

def do_beg(pet, time_passed):
    print "I'm bored!"

def do_dead(pet, time_passed):
    print '...'

STATE_TO_FUNC = dict(Waiting=do_waiting,
                     Sleeping=do_sleep,
                     Feeding=do_feed,
                     Playing=do_play,
                     Howling=do_howl,
                     Begging=do_beg,
                     Dead=do_dead
                     )

class Pet:
    def __init__(self):
        self.state = 'Waiting'
        self.hunger = 1.0
        self.tiredness = 1.0
        self.happiness = 1.0

    def process(self, time_passed):
        self.hunger +=1*time_passed
        self.tiredness +=1*time_passed
        self.happiness -= 1*time_passed

        func = STATE_TO_FUNC[self.state]
        new_state = func(self, time_passed)
        if new_state is not None:
            self.set_state(new_state)

        if self.hunger >10:
            self.set_state('Dead')
        elif self.hunger > 5 and not (self.state == 'Feeding'):
            self.set_state('Howling')
        elif self.tiredness > 5:
            self.set_state('Sleeping')
        elif self.happiness < 0 and not (self.state == 'Playing'):
            self.set_state('Begging')

    def set_state(self,state):
        if not self.state == 'Dead':
            self.state = state

from msvcrt import getch
import time
pet = Pet()
while True:
    time0 = time.time()
    cmd = getch() # what command?
    pet.process(time.time()-time0)
    if cmd == 'a':
        pet.set_state('Feeding')
    if cmd == 's':
        pet.set_state('Sleeping')
    if cmd == 'd':
        pet.set_state('Playing')
wisty