Tkinter "intervention" in a logic loop - python

So, I'm not sure the title describes my issue in a comprehensive manner.
I have made a working "loop" code in Python (even with working recursive functions) for some sort of a card game "AI". See the following pseudo-code:
def move():
if Turn == "Computer":
if Computer cannot play:
return False
else:
Do some "AI" stuff... the computer plays
Turn = "Player"
move()
elif Turn == "Player":
if Player cannot play:
return False:
else:
in = input("Select Card: ")
****So here the loop stops and waits for the input from the player
...then the input is evaluated and "played" as a player move
Turn = "Computer"
move()
while continue:
continue = move()
So if the computer can play, it plays the card, the turn changes to Player and the move function is called again, otherwise false is returned and the PC-Player back and forth turn is over. In the Player's turn the loop stops to wait for the user input from terminal and then it continues...
So far everything works, but now I want to implement a GUI for player input. I have some basic knowledge with Tkinter and so far I created buttons on screen rapresenting the player's cards. The question is how can I "stop" the loop waiting for the user to click a button (corresponding to the card - or the PASS TURN). Maybe with some sort of threading but sounds complicated... there must be an easier solution like in the "Terminal input version" that I have already working.
Thanks

You can't use a loop in a GUI, because the GUI has it's own loop that needs to run. The mainloop() in tkinter. You need to shift your planning to "event driven" programming. It sounds like in your case the event would be the user choosing a card.
def player_chooses_card():
# graphic and logic updates for player's choice
# computer turn
if Computer cannot play:
trigger graphic for end of game
else:
Do some "AI" stuff... the computer plays
# function is done; back to idle state waiting for an event
FWIW making a loop with recursion is bad style in python.

I feel your pain on the Tkinter loops. It was a hard lesson for me to learn as well. Loops such as while and for DO NOT WORK in tkinter. The way I typically solve this problem is with "root.after." Take the code in your loop and define it as a command (adding root.after(1, name of your command) like so:
def myloop():
move()
root.after(1, myloop)
Then, call it for the first time right before 'root.mainloop', like so:
<your other code>
root.after(1, myloop)
root.mainloop()
Also keep in mind that Tkinter does not make nice nice with multiprocessing/threading within mainloops, so you have to set up each process/thread as its own canvas and root.
Finally, you could set up a series of if statements and variables to separate your loop into chunks so that it stops and starts based on player input. For example,
def myloop():
if playerhasdonething == True:
move()
else:
print("waiting on player")
root.after(1, my mainloop)
I apologize if I missed the point of your question, this is how I personally would try to solve your problem

Related

python method not being called

I'm writing an automated test using Selenium Python that will play a Web-based game of tic-tac-toe. The method checkForWinner() needs to check the UI for a line of text displaying the winner after each click, but the method isn't getting called, and I don't know why.
def checkForWinner(self, load_browser):
if Tags.resultOh:
winner = 'O'
LOGGER.debug('Winner O')
assert winner
elif Tags.resultEx:
winner = 'X'
LOGGER.debug('Winner X')
assert winner
elif Tags.resultTie:
winner = 'None'
LOGGER.debug('Tie')
assert winner
else:
self.test_playGame(load_browser)
Whenever the script is run, the game reaches a conclusion and the script hangs. The browser should close after the game is over, but it doesn't. It's obviously waiting for a condition that's not being met, but I can't see what it is.
I think you're getting stuck inside your for loop, where you're calling the test_PlayTTT() method recursively. Since you're playing one bot against another, you need to keep track of the state of the game board after each bot moves. Right now, it looks like you're only tracking the moves of this bot, but it can potentially click on squares already claimed by the other player. You want clickedSquares to contain squares clicked by the opposing bot, not just this one.
The maximum number of squares either player can mark is five in a game of tic-tac-toe, so either check for a winner after each player clicks a square, or call checkForWinner() when the size of clickedSquares reaches 5.

How to pass input from one entry widget into multiple sequential functions OR how to pass button input into a function

So I essentially have 2 codes at the moment, a text-based game and a GUI, that function separately that I'm trying to combine. The game basically has a series of functions it can call based on user input - eg:
def game():
print("Would you like to play? [Y/N]")
start = inpupt().lower().strip()
if start == 'y':
adventure()
elif start == 'n':
goodbye()
with adventure() calling two or more options leading to their respective functions, etc. etc.
The GUI is tkinter based, has a label, a scrolled text box, an entry widget, and an "enter" button. I've created my own "print" functions that allow the code to input text into the scrolled text box, and then disable the box again, so that the user doesn't directly edit it. I've also been able to have the button/enter key pull text from the entry widget and print it in the text box, and then clear the entry widget.
What I don't know how to do is get the entry widget/button to integrate with the different game functions. It seems like you can set a function for the button - which is how I was able to pull the entry text, but I don't know how to then pass that pulled information on to another function.
I tried something like this which worked for just entering text into the scrollbox with this attached to the button as the command:
def clicked(self, variable, event=None):
self.variable = self.userin.get()
self.print1(self.variable)
self.clear_text()
But then I don't know how to call that as a part of my game function. This was all I could think of (and I know it's wrong and bad, ok?):
def game():
print1("Would you like to go on an adventure? [Y/N]")
clicked(self, start) #trying to use "start" as the variable
if self.start == "y":
self.print1("Let's go!")
elif self.start == "n":
self.print1("Okay, bye!")
else:
self.print1("What?")
But then game() is trying to call the clicked() function instead of waiting for the button, so it's not really pulling the input from the user. I also kept getting the error that "start" was not defined, probably for that reason. (Also please ignore any errors in where "self" is supposed to go... this was my first stab at using classes, and I know I'm probably doing more wrong than right, I'd figure it out if you had a solution in this vein)
So is there a way to get the button to pass the user input into the function, without the function being exclusive to the button? (Like I know I could get it to make one choice, but then how would I get the button to function for the next choice?)
My "Plan B" was to just create buttons for user input - either yes/no or a/b/c/d - so that the function of the buttons would be the same : return "B" (or something like that), but then how would I get the function to pull that input? Or wait for that input?
I have googled this six ways to Sunday, and have not found anything that works for this situation. I thought I'd found something with the CallBack function - but then I realized they were using it for multiple buttons, instead of the same button for multiple functions.
Here is a quick example using some of what you provided that should demonstrate how you can link your conditions for the button. Regardless of how you actually structure your code, the concept should demonstrate one way you could do it. I would look into some tkinter tutorials as these concepts are well documented. I hope this helps.
import tkinter as tk
def clicked():
entry_value = entry.get()
entry.delete("0", tk.END)
print(entry_value)
# set up whatever conditions you want the button to be linked with
if str(entry_value) == "y":
print("Alright, get ready!")
# now you call your function to start the game here
start_game()
elif str(entry_value) == "n":
print("Ok, maybe another time")
label.config(text="Ok, maybe another time, closing the game now")
root.after(2500, lambda: root.destroy())
else:
label.config(text="Invalid entry, please enter a lower case 'y' or 'n'")
label.after(3000, reset_label)
def start_game():
# just an example this creates a new window to simulate some bhavior of the game
top = tk.Toplevel()
top.geometry("500x500")
top.title("New Game")
top.config(bg="blue")
# add things to the toplevel that shows the game? not sure what you want to do
def reset_label():
label.config(text="Would you like to play? [y/n]")
root = tk.Tk()
root.geometry("300x200")
label = tk.Label(root, text="Would you like to play? [y/n]")
label.pack()
entry = tk.Entry(root)
entry.pack()
button = tk.Button(root, text="Go", command=clicked)
button.pack()
root.mainloop()
Thank you for the suggestions! This is what I worked out (once I figured out the right way to change the command - no extra parenthesis!):
def choice1(event=None):
whereto = userin.get().lower().strip()
if whereto == "woods":
print1("The woods are spooky")
button["command"] = woods
elif whereto == "tavern":
print1("The tavern is crowded")
button["command"] = tavern
else:
print1("What?")
print1("Woods or Tavern?")
def clicked(event=None):
start = userin.get().lower().strip()
print1(userin.get())
clear_text()
changelabel("Let's go!")
if start == "y":
print1("Lets go!")
print1("Woods or Tavern?")
button["command"] = choice1
elif start == "n":
print1("Ok bye!")
else:
print1("What?")
print1("Do you want to play a game?")
I'd like to make the return key work the same as the button click, so I suspect I'm going to have to add the root.bind('<Return>', func) everywhere too

How to implement turns while still executing the main loop?

I am creating a trading game in Python, and want to know how to implement turns without pausing the gameloop. I know that I will have to change the way movement is implemented, but how would I do that?
Note: code can be reached here (May be old): http://pastebin.com/rZbCXk5i
This is usually done with something called a game state machine
What that is, is extremely simple. I can show you with an example.
def main_game_loop():
if state == "player_turn":
# logic for player's turn
elif state == "enemy_turn":
# logic for enemy's turn
# they can also be used for other things, such as where you are in the game
elif state == "paused":
# pause logic etc etc

How to ignore user input during sleep in Python 3?

I'm new to programming and am stuck with this problem in a small RPG game I'm making. It's mostly text based but uses tkinter for a simple GUI.
During battles, I have a for loop that goes through all creatures that are participating in the battle, and executes their action one by one with small sleeps between each action. My problem is that when the user presses keys or clicks buttons, the commands are buffered and executed after the for loop completes. Is there an easy way to ignore the user commands while this loop is going? I've tried unbinding keys and disabling buttons, then re-enabling them when the loop is complete, but it still doesn't work. I've also read about flushing the user input but I can't figure it out. Since I'm a rookie, I think I'm missing some basic concept here.
Here's my code:
def battle_round(self, command):
''' (Battle, command) -> NoneType
A round of battle where every creature gets an action.
'''
# Pause player input with mode variable.
self.player.mode = 'wait'
# Keep track of number of rounds.
self.round_number += 1
# Get initiative order for all mobs, party members, and player.
ordered_creatures = self.initiative()
# Get commands from each mob.
for mob in self.mobs:
mob.command = self.determine_mob_command()
# Check battle speed option.
delay = 1
# Begin actions for all creatures.
for creature in ordered_creatures:
# Delay between actions. Write a space between lines.
self.text_window.update_idletasks()
self.write(self.text_window, '\n')
time.sleep(delay)
# Player action.
if type(creature) == Player:
if command == 'standard_attack':
self.standard_player_attack()
if command == 'retreat':
self.retreat()
if type(creature) == PartyMember:
pass
# MOB action.
if type(creature) == MOB:
if creature.command == 'standard_attack':
self.standard_mob_attack(creature)
self.start_next_round()
I'm using Python 3.2.3 with Tk 8.5 and IDLE 3.2.3. My OS is Windows 7.
Thanks in advance!
Edit: Thanks for the replies so far guys. I may be in over my head here since I didn't even know what threading was until just now, and I'm not sure how I would go about reading and ignoring user input. As far as the code for the user input goes, I have a lot of it. I'll copy and paste some here:
def attack():
if player.in_battle == True:
if player.mode != 'wait':
player.current_battle.battle_round('standard_attack')
def retreat():
if player.in_battle == True:
if player.mode != 'wait':
player.current_battle.battle_round('retreat')
# Set up battle buttons.
attack_button = Button(battle_button_frame, text='(A)ttack', command=attack,
width=10,)
attack_button.grid(column=1, columnspan=1, row=2, padx=5, pady=5)
retreat_button = Button(battle_button_frame, text='(R)etreat', command=retreat,
width=10,)
retreat_button.grid(column=2, columnspan=1, row=2, padx=5, pady=5)
battle_button_list = [attack_button, retreat_button]
These buttons, for example are to have the user either attack the selected monster, or attempt to run away from the battle.
I also have several key bindings:
# Bind Keys
root.bind('<Escape>', func=keyboard_cancel)
root.bind('<Control-Key-m>', func=keyboard_move_mode)
root.bind('<Control-Key-M>', func=keyboard_move_mode)
root.bind('<Control-Key-l>', func=keyboard_look_mode)
root.bind('<Control-Key-L>', func=keyboard_look_mode)
root.bind('<Control-Key-t>', func=keyboard_talk_mode)
root.bind('<Control-Key-T>', func=keyboard_talk_mode)
root.bind('<space>', func=keyboard_begin_battle)
root.bind('<Left>', func=arrow_key_select_left)
root.bind('<Right>', func=arrow_key_select_right)
My problem remains that when the for loop with the sleeps is going, if the user presses a button or uses a key binding, it will get executed as soon as the battle round is over. (About 7 seconds if there are 6 monsters and the player.) I am a complete beginner at this so I apologize for not being clear with my post and for my code being a horrible mess.

How to create a "play again" option with Pygame

I am new to programming with Python. I have been working through a tutorial book that I found, and got the game up and running, but then decided I wanted to have a "Play Again?" option at the end. I can get the game to quit out with a press of the "n" key, but cannot work out how to get the game to restart.
Here is the code I think is giving the trouble:
#player reaches treasure
if player_rectangle.colliderect(treasure_rectangle):
#display text
screen.blit(text,(screen_width/2-195,screen_height/2-25))
if event.type==pygame.KEYDOWN:
if event.key==pygame.K_n:
exit()
elif event.key==pygame.K_y:
pygame.display.update()
I know something needs to go after the elif event, and I have tried all that I can think of. I tried to define the whole program, and call it but that stopped the whole thing running. I have looked around internet sites, but just cannot seem to come up with a answer.
Can some one help out in easy terms how to get the game to restart to the starting position when the y key is pressed? I know it has something to do with a loop, I just cannot place my finger on what.
Many thanks.
It's not entirely clear how your code is organized, so I'll be very general. Usually games are implemented with a "main loop" that handles all of the action. In "pseudo"-python:
def main_loop():
while True:
handle_next_action()
draw_screen()
if game_is_over():
break
Before you start the loop, you usually do some setup to get the game state how you want it:
def main():
setup()
main_loop()
shut_down()
Given those parts, you can reset the game by having the main loop code call setup again (it may need to be specifically designed to be runable more than once):
def main_loop():
while True:
handle_events()
draw_screen()
if game_is_over():
if play_again(): # new code here!
setup()
else:
break
You might want to split the setup code into two parts, one which only needs to be run when the program begins (to read configuration files and set up things like the window system), and a second that gets repeated for each new game (setting up the game's state).
To restart a game you normally need to reset all variables to their initial value (e.g. number of lives, score, initial position, ...).
The best way is to put all initialisations inside a procedure:
def init():
global score,lives # use these global variables
score=0
lives=3
init() # call init() to define and reset all values
while 1: # main loop
...
elif event.key==pygame.K_y:
init() # restart the game

Categories

Resources