0

I'm writing a text-based RPG for a class, and am stuck on a code predicament...

from tkinter import *
...
runOnce = False
nextRun = False
"""Main Loop"""

while True:
    #New Game initialize
    if you.rName == None and runOnce == False:
        log("What is your name, adventurer?", eventLog)
        runOnce = True
    if you.rName != None and nextRun == False:
        log(f'What is your profession, {you.rName}?', eventLog)
        nextRun = True

#keypresses
playerInput.bind("<Return>", keyPress)
playerInput.bind("<FocusIn>", focusIn) 

top.update()
top.update_idletasks()

What I have currently works, but there are a ton more if-statement type situations that need responses before continuing to the next statement. The loop is to continuously update the GUI as the game is run.

How can I code the something that needs a response once within a while loop efficiently?

jon compton
  • 21
  • 1
  • 7
  • What should the program do in case `runOnce` is `True` initially? I do not see how the above will "terminate" after the first run. – Willem Van Onsem Nov 12 '18 at 19:40
  • Oh that's my mistake, I initialized runOnce outside of the while loop. – jon compton Nov 12 '18 at 19:42
  • 2
    If a thing is only supposed to happen once, then why is it in a `while` loop? It sounds like it should be a simple `if/else` conditional? If it needs to be in a loop, then you can tell it to `break`, as in [this answer](https://stackoverflow.com/questions/41065354/break-for-loop-in-an-if-statement) – G. Anderson Nov 12 '18 at 19:42
  • You can use a generator – Lucas Wieloch Nov 12 '18 at 19:44
  • Yeah, as you have it right now this will print once and then loop forever, checking the conditional in line 3 until the universe collapses – Steve Archer Nov 12 '18 at 19:44
  • @G.Anderson the while loop is because the code is for a game loop, using tkinter for gui – jon compton Nov 12 '18 at 19:44
  • @G.Anderson it's pretty common to need to do this, although the example in this question is a little contrived. – paddy Nov 12 '18 at 19:45
  • 2
    Rather than `while True:`, you could give it actual criteria: `while run_once == False:` – G. Anderson Nov 12 '18 at 19:46
  • @G.Anderson Again, the while True: is the game loop itself, not the part I want to run once – jon compton Nov 12 '18 at 19:49
  • 2
    I think all the comments so far have missed the point. Maybe the example should be clearer. In other languages the need for this type of pattern often arises, in particular to do something once inside a loop in cases where moving it outside the loop would result in a lot of code duplication. – paddy Nov 12 '18 at 19:51
  • Apologies, but you don't specify anywhere in your question that the code _must_ be in a `while` loop, or _must_ be a `while True:`. So, given that, if you need to break out of a loop, you would use `break` – G. Anderson Nov 12 '18 at 19:51
  • @G.Anderson I thought that at first as well, but how would you re-enter the same while loop? (if I break out of the if statement, that would defeat the purpose of simplifying it, due to the fact that I'm trying to find a catch-all solution for a run-once-in-a-while-loop) – jon compton Nov 12 '18 at 19:53
  • 1
    Short answer: You don't. I think it would be good to add a more well-explained use-case in your question. You state that you only want the loop to run once, so you achieve that with `break`. If you need to do some parts more than once, you would need to nest loops and only `break` out once you no longer need the outer loop – G. Anderson Nov 12 '18 at 19:57
  • Why don't you move the code you want to run once to just before the while loop? `print("Do the thing only once") while True: ...` – CamJohnson26 Nov 12 '18 at 19:57
  • @CamJohnson26 2 reasons, 1. I have multiple do the thing only once's, each needing a user response, which brings to 2. responses to those do the things need updated. – jon compton Nov 12 '18 at 20:03
  • Oh so you're trying to get user input? You could use ternary operators: `userInput = input("Enter your input: ") if userInput is None else userInput` – CamJohnson26 Nov 12 '18 at 20:06
  • @CamJohnson26 That could work, I'll try that out thanks! – jon compton Nov 12 '18 at 20:08
  • I think it'll be worthwhile to provide a more specific example in your question to understand what you're trying to do. I feel like this could well be an [XY Problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem#66378). – r.ook Nov 12 '18 at 20:10
  • @Idlehands would it be best to re-post this question? It kind of is an XY now that I understand how the question is so misleading... – jon compton Nov 12 '18 at 20:15
  • I think editing it with proper clarification would suffice. Stackoverflow generally frowns upon re-posting the same question twice, unless you plan on deleting this one first. – r.ook Nov 12 '18 at 20:16

2 Answers2

0

Seeing the clarification, I agree with the comments these type of actions shouldn't belong in the loop. These should be all data that are collected prior to the main game loop.

If you're looping through these to validate for inputs, you can have separate loops instead:

while not you.name:
   you.name = input('Enter name: ')
   # some additional validation text if necessary...

while not you.job:
   you.job = input('Enter job: ')
   # some additional validation text if necessary...    
while True:
   # main game loop requiring you.name, you.job

Another approach is a bit contrived. You can pre-define these functions before your mainloop and create a RunOnce class to only execute these functions once:

class RunOnce(object):
    def __init__(self, func):
        self.func = func
        self.ran = False

    def __call__(self):
        if not self.ran:
            self.func()
            self.ran = True
    # once the function has been called, 
    # flip self.ran so it won't fire again.

# Decorate your functions with this special class
@RunOnce
def get_name():
    you.name = input('Enter Name: ')

@RunOnce
def get_job():
    you.job = input('Enter Job: ')

And then when you are in the main game loop:

while True:
    get_name()    # this will run once
    get_job()     # this too will run once
    get_name()    # this won't run anymore
    get_job()     # this neither.

The benefit of this approach is it gives you the flexibility to re-run the function if necessary:

get_name()             # get_name.ran becomes True
get_name.ran = False   # reset the flag
get_name()             # this will run again.

I'd still say it's much better to just restructure your code so that things that only need to be captured once stay outside the main loop.

r.ook
  • 13,466
  • 2
  • 22
  • 39
  • The decorator approach will definitely work better than what I've got. How could I restructure the code to stay outside the main loop as you're saying? – jon compton Nov 12 '18 at 20:50
  • Of course the downside of that is the function must exist outside of the main loop, so if your loop reads like a story format, it'll become a bit harder to trace and read. But honestly IMO whatever works best for you then it's good. – r.ook Nov 12 '18 at 20:51
  • re:restructuring, for anything *initial* that needs to only be captured once, just capture them prior to the main loop as in my answer. But if there are halfway points info capture (e.g. divergence in a path) then you'll have to work into the loop either through the decorator approach or your `if... else` blocks. – r.ook Nov 12 '18 at 20:54
  • 1
    OOHH That will work perfectly I see what you mean. Thank you so much – jon compton Nov 12 '18 at 20:58
-1

Try checking your parameters for null before using them. If you're looking for user input you can do this:

userInput = None
while True:     
    userInput = input("Do the thing only once") if userInput is None else userInput
    ...
CamJohnson26
  • 1,119
  • 1
  • 15
  • 40
  • 2
    Honestly I feel like OP's implementation is cleaner and more readable. Also this wouldn't work if it was a specific function (e.g. `print()`). – r.ook Nov 12 '18 at 20:12