How to fix the game function? - python

im beginner on python and as a practice i want to solve this ( https://www.practicepython.org/exercise/2014/07/05/18-cows-and-bulls.html ) by tkinter
when i run this code and press start button it hangs and no error is shown in pycharm, even when i debug
obviously the bug is in game code
can you help me with this?
from tkinter import *
import random
def entry_box():
global user_guess
start_button.destroy()
user_guess=Entry(win,font='Bradly 12', width=25)
user_guess.insert(END,'0000')
user_guess.place(x=150,y=150)
game()
def game():
global user_guess , result
digit=[]
num =[]
counter = 0
for i in range(4):
digit.append(random.randint(0,9))
while num != digit:
num = list(user_guess.get())
cow = 0
bull = 0
for i in range(4):
if int(num[i]) in digit:
if int(num[i])==digit[i]:
cow += 1
else:
bull += 1
counter += 1
result.set('cow = %d \n bull = %d'%(cow,bull))
result.set('WELL DONE! YOU FOUND IT AFTER %d GUESS ' %counter )
win = Tk()
win.title('Let\'s play')
frame = Frame(win, height=300, width=500, bg='lightblue')
frame.pack()
result = StringVar()
result.set('cow=0\nbull=0')
start_button = Button(win , text='start', font='Broadway 15',command = entry_box)
start_button.place(x=200, y=175)
results = Label(win, textvariable=result, font='Broadway 20',bg='lightblue', fg='darkblue')
results.place(x=200 , y=0)
win.mainloop()

Your code hangs because of multiple reasons:
as #furas mentioned, num != digit will always be False, because you compare list of chars with list of digits;
Even if you fix this, your code will just never exit the loop: you will compare the first list [0,0,0,0] with the random one (num) and unless they are equal, you will get False. Then the code takes the same two lists, and compares them infinitely, with always the same outcome. You can see this easily if you add a print statement in the while loop.
This is one possible solution:
import tkinter as tk
import random
class MainGame(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.main_frame = tk.Frame(self, height=300, width=500, bg='lightblue')
self.main_frame.pack()
self.result = tk.StringVar()
self.result.set("Cow=0\nBull=0")
self.start_button = tk.Button(self.main_frame, text='start', font='Broadway 15', command=self.entry_box)
self.start_button.place(x=200, y=175)
self.results = tk.Label(self.main_frame, textvariable=self.result, font='Broadway 20', bg='lightblue',
fg='darkblue')
self.results.place(x=200, y=0)
def entry_box(self):
self.start_button.destroy()
self.user_guess = tk.Entry(self.main_frame, font='Bradly 12', width=25)
self.user_guess.insert(tk.END, '0000')
self.user_guess.place(x=150, y=150)
self.start_game()
def start_game(self):
user_input = self.user_guess.get()
num = [int(cypher) for cypher in user_input]
counter = 0
game_playing = True
while game_playing is True:
digit = [random.randint(0, 9) for i in range(4)]
cow, bull = 0, 0
for i in range(4):
if num[i] in digit:
if num[i] == digit[i]:
cow += 1
else:
bull += 1
counter += 1
print("Turn {}: cows = {}\t bulls={}".format(counter, cow, bull))
if cow == 4:
print("digit = {}\t num = {}".format(digit, num))
game_playing = False
if __name__ == "__main__":
game = MainGame()
game.mainloop()
First thing: do not use global imports (from tkinter import *). Instead use import tkinter as tk.
Then let's look at your entry_box: the function as you defined it takes no argument, but then you expect it to delete the start_button: the only reason this works is because the function is defined in the same script, but as soon as you move it to another script for example, it will fail. You did the same with result, which is defined in the main body, but then used in game, and for the win variable used to set the user_guess Entry widget.
The solution for this is to 1) reformat your code to account for these changes, which can work for something this small 2) use global variables, but this is usually considered a bad practice, because as your code grows it becomes a pain to track them down (plus many other reasons) 3) use classes, which is what I did here.
So here is what I did: I create a class, and in the __init__ I put the things I want to have available when the script starts, or that I generally want to use throughout the game: the main frame where I will place the widgets, the results or the start button.
Then the entry_box function: You see that I pass just self: since the start button was defined as self.start_button in the __init__, there is no reason to pass it as an argument, because it is already stored in self as an attribute. Same with the main_frame where I put the Entry widget.
Lastly the game function: here, I make sure that the comparison between num and digit is between int numbers. Then if the comparison fails, I draw another random list of 4 elements for digit and repeat. The results are printed in the terminal.
This is by no means perfect, and you can easily improve it in a few ways:
1) the comparison between the lists can be easily made faster and shorter with numpy;
2) I did not update the results widget. You should be able to do it with what I told you here.
3) I assume you want the user to input the user_guess. You just have to modify a bit this code
Hope it helps!

Related

How to use bind "<Return>" to call a function

I know my question sounds very much like many previous questions but I can honestly not figure it out in the context of my program. I have an algorithm for the Collatz Conjecture that I would like to run through a Tkinter GUI (everything works just fine through the terminal).I have tried to bind the relevant function to the Return key and to a button but I get the same error message for both methods of entering data, which I will show below. I get the output to work perfectly on the GUI if I input through the terminal.
What I have tried is best explained through the code below. (The code above the #### line has mostly to do with making the GUI appear over my Spyder IDE and not hiding behind it.)
Code:
from tkinter import *
root = Tk()
import os
import subprocess
import platform
def raise_app(root: Tk):
root.attributes("-topmost", True)
if platform.system() == 'Darwin':
tmpl = 'tell application "System Events" to set frontmost of every process whose unix id is {} to true'
script = tmpl.format(os.getpid())
output = subprocess.check_call(['/usr/bin/osascript', '-e', script])
root.after(0, lambda: root.attributes("-topmost", False))
########################################################################
lst = []
def collatz(num):
while num != 1:
lst.append(num)
if num % 2 == 0:
num = int(num / 2)
else:
num = int(3 * num + 1)
def main(event):
collatz(num)
#Input Box
input = Entry(root, width = 10, bg = "light grey")
input.grid(row = 0, column = 0, sticky = W)
input.get()
input.bind("<Return>", main)
##Button
#button1 = Button(root, width = 10, text = "Run", command = main)
#button1.grid(row = 1, column = 0, sticky = W)
##Output box
output1 = Text(root, width = 100, height = 10, bg = "light grey")
output1.grid(row = 3, column = 0, sticky = W)
output2 = Text(root, width = 50, height = 1, bg = "white")
output2.grid(row = 2, column = 0, sticky = W)
output1.insert(END, lst)
output2.insert(END, "Number of iterations are: " + str(len(lst)))
########################################################################
raise_app(root)
root.mainloop()
When I run the code as is, the input box appears but when I click return, I get an error message:
Exception in Tkinter callback
Traceback (most recent call last):
File "/anaconda3/lib/python3.7/tkinter/__init__.py", line 1705, in __call__
return self.func(*args)
File "/Users/andrehuman/Desktop/Python/programs/Collatz Conjecture/Collatz_alt3.py", line 43, in main
collatz(num)
NameError: name 'num' is not defined
Exactly the same if I try and link a button to the "main" function.
When I comment the input and buttons out, and enter the number through the terminal, everything works as expected. The list of iteration numbers appear in the text box as it should. (And I can even get a Matplotlib graph to display the data visually in the terminal.) If I can get this problem sorted out I want to try and display (or embed) the Matplotlib graph in the GUI.
Anyway, that's it. Any help will be greatly appreciated.
Andre Human
name 'num' is not defined occurs because you're calling collatz(num), but the program does not understand what value you are referring to when you say num. You should assign a value to that name before using it. I assume you want the value to be the contents of your input box.
def main(event):
num = int(input.get())
collatz(num)
You will also need to copy your output1.insert and output2.insert lines to the inside of main. Right now, those lines execute before the window even appears to the user, so there's no way that they can enter a number fast enough to get collatz to trigger before the text gets written. And changing lst after the fact does nothing to the text, since it's not smart enough to notice that the list has changed.
def main(event):
num = int(input.get())
collatz(num)
#delete previous contents of text boxes
output1.delete(1.0, END)
output2.delete(1.0, END)
output1.insert(END, lst)
output2.insert(END, "Number of iterations are: " + str(len(lst)))
Another problem is that successive calls to collatz will cause lst to grow and grow, because the contents of the list from previous calls is still present. Try entering 4 into the text box, and press Enter a few times. The output will go from 2 to 4 to 6... That's not right.
This is something of a natural hazard when using mutable global state. One possible solution is to reset lst at the beginning of each collatz call.
def collatz(num):
lst.clear()
#rest of function goes here
... But I'm more inclined to make lst local to the function, and return it at the end.
def collatz(num):
lst = []
while num != 1:
lst.append(num)
if num % 2 == 0:
num = int(num / 2)
else:
num = int(3 * num + 1)
return lst
def main(event):
num = int(input.get())
lst = collatz(num)
#delete previous contents of text boxes
output1.delete(1.0, END)
output2.delete(1.0, END)
output1.insert(END, lst)
output2.insert(END, "Number of iterations are: " + str(len(lst)))
#later, just before mainloop is called...
#lst doesn't exist in this scope, so just set the text to a literal value
output1.insert(END, "[]")
output2.insert(END, "Number of iterations are: 0")

Python Text-Based Game with Tkinter

I participated in a local programming competition yesterday, where I was put on a team of four with members of varying skill levels. We were trying to make a text-based Zork style game, but since UI was one of the categories I was put on creating a GUI which I have no experience with in python. I used Tkinter and got everything mostly working inside the GUI itself, but can't figure out how to get the GUI to interact with the rest of the code.
I'm just including the relevant bits here, partially because the rest of the code is quite a mess, with no comments and lots of syntax errors, but I can post it if needed. Here's the code:
def pinput():
global e
player_input = e.get()
print(player_input)
def progress():
hallway_1()
def hallway_1():
global player_input
global prin
global e
prin.set("You are in a hallway. Yellow lockers line the sides of all walls and you see three doors. One leads into a Library, one to a History classroom, and one to a Math room. You can 1.) Search the hallway or 2.) Procede to one of the rooms.")
while player_input != "1" and player_input != "2": #Best guess at a way to get the code to wait for input, actually just causes code to freeze
pass
if player_input == "1": #Even if 1 is in the entry box beforehand, it never gets here
prin.set("You search through the lockers and find 1 health potion.\n Would you like to add it to your inventory? 1.) Yes 2.) No")
player_input = "999"
def player_attack():
global enemy_hp
enemy_hp -= player_strength
prin.set("You just attacked the " + enemy_name + "!")
def player_block():
global block
prin.set("You predict that your enemy is going to attack and decide to block")
block = 1
def player_use():
global player_item_use
global item_name
player_item_use = 100
prin.set("You decide to use one of your items. What do you use?")
while player_turn_input != 0 or 1 or 2 or 3 or 4 or 5 or 6 or 7 or 8 or 9 or 10 or 11 or 12 or 13 or 14 or 15 or 16 or 17 or 18 or 19 or 20 or 21 or 22 or 23 or 24 or 25 or 26:
player_item_use = e.get()
time.sleep(1)
item_name = Inventory[player_item_use]
item(item_name)
def run_away():
global player_hp
global lost_item_1
global lost_item_2
prin.set("You decide to run away from the " + enemy_name + "!\nThis causes you to lose 20 health points and two inventory items.")
player_hp -= 20
if backpack == True:
lost_item_1 = random.randint(1,27)
lost_item_2 = random.randint(1,27)
else:
lost_item_1 = random.randint(1,9)
lost_item_2 = random.randint(1,9)
RemoveInventory(lost_item_1)
RemoveInventory(lost_item_2)
library()
top = tk.Tk()
prin = tk.StringVar()
#path = "smalllibrary.png"
#tkimage = ImageTk.PhotoImage(Image.open(path))
#displayimage = tk.Label(top, image=tkimage).grid(row=0)
inv = tk.Text(top, width="24")
inv.grid(row=0, column=1)
for x in Inventory:
inv.insert("end", str(x) + str(invnum) + '\n')
invnum += 1
healthbar = tk.Label(top, text=("Health: " + str(player_hp))).grid(row=1, column=1)
text = tk.Label(top, bg="black", fg="white", textvariable=prin, justify="left", cursor="box_spiral").grid(row=1, sticky="w")
e = tk.Entry(top)
e.grid(row=2)
e.focus_set()
Attack = tk.Button(top, text ="Attack", activebackground="red", width="10", command = player_attack).grid(row=4, sticky="e")
Block = tk.Button(top, text ="Block", activebackground="red", width="10", command = player_block).grid(row=5, sticky="e")
Use = tk.Button(top, text ="Use Item", activebackground="red", width="10", command = player_use).grid(row=4, column=1, sticky="w")
Run = tk.Button(top, text ="Run", activebackground="red", width="10", command = run_away).grid(row=5, column=1, sticky="w")
Enter = tk.Button(top,text='Enter',command=pinput).grid(row=3)
Progress = tk.Button(top, text="Progress", activebackground="green",
prin.set("REDACTED")#This had my team's full names
top.mainloop()
So, there are a few problems. I added the "progress" button for testing purposes, ideally the game would start in Hallway 1. However, I couldn't figure out where to put the hallway_1() function for this to work. Before the top.mainloop() and the GUI wouldn't open. After it, and e.get() threw TCL errors.
So, I bound it to a button. However, now the code keeps getting stuck in the "while" loop even if 1 is inputted into the entry box before the code runs. I'm perplexed by this because Visual Studio reports player_input as being '1' in the Autos box, but it keeps running through the loop anyway.
You can't do a while loop like that in a GUI event handler. Until you return from the handler function, the GUI doesn't get to update its display, accept user input, or do anything else, which means player_input will never change once you get there. A handler function needs to do one thing, set up anything needed for future handlers, and return immediately.
It's hard to get your head around thinking in terms of the event loop and handler callbacks the first time you write a GUI, but it's absolutely essential; until you do, things seem bizarre and arbitrary.
So, the answer isn't to "wait for input", but to set up a handler that fires when that input is ready. (You can instead set up a handler on an after function so it fires every N milliseconds instead of firing on input, but that usually just makes things more complicated.)
One way to do this is to have the "Progress" button trigger "the next step", and keep track of some state that lets you determine what "the next step" is. An in fact, you already have that state: it's whether the player has already given correct input or not. So:
def pinput():
global player_input
player_input = e.get()
print(player_input)
def progress():
pinput()
if player_input != "1" and player_input != "2":
hallway_1_enter()
else:
hallway_1_doit()
def hallway_1_enter():
global player_input
global prin
global e
prin.set("You are in a hallway. Yellow lockers line the sides of all walls and you see three doors. One leads into a Library, one to a History classroom, and one to a Math room. You can 1.) Search the hallway or 2.) Procede to one of the rooms.")
def hallway_1_doit():
if player_input == "1": #Even if 1 is in the entry box beforehand, it never gets here
# etc.
This is obviously a bit of a clunky design, but it's the smallest change to your existing design that gets you past the first step. Once you get the hang of it, you should be able to improve it from there.
Also notice the global player_input I put in pinput. Your existing function didn't have that, so it was just creating a local variable with the same name, which then goes away immediately; the global never gets changed. Any function that wants to assign to a global needs the global statement.

How to print Python Output in Entry widget?

I've searched through the whole Internet for how to do this, and nothing came to me. There were some similar topics, when programmers asked of how to parse 'int' numbers to the Entry output. But it is much simpler because you just use getters, then insert() - and voila.
I am trying to do the following thing. I print the text written in one line. And for each word I want to count how many times it appeared in the same text.
E.g., I print in my first Entry "one two one two three" - I get "0 0 1 1 0" in the second Entry widget.
Any non-space sequence of characters is considered a word.
from tkinter import *
class DM_3_1:
def __init__(self):
root = Tk()
root.geometry('250x150')
root.title("DiscreteMaths_3_1")
usertext = StringVar()
Label_1 = Label(root, text="Input")
Label_2 = Label(root, text="Output")
inputField = Entry(root, textvariable = usertext)
outputField = Entry(root)
inputField.bind('<Return>', lambda _: printLine())
def printLine():
counter = {}
for word in inputField.get():
counter[word] = counter.get(word, 0) + 1
Ans = print(counter[word] - 1, end=' ')
outputField.insert(0, str(Ans))
Label_1.grid(row = 0)
Label_2.grid(row = 1)
inputField.grid(row = 0, column = 1)
outputField.grid(row = 1, column = 1)
root.mainloop()
DM_3_1()
What I get in the output now: Here is the screenshot
As far as you can see, the application works, but there's 'NoneNoneNone...'(depends on the number of characters, including whitespaces) instead of '0 0 1 1 0'. How do I solve my problem? Where's a logical mistake? I guess, it's about the function, but I don't actually see the mistake.
You have set Ans to be equal to print rather than the value it was supposed to be. Also your for loop was getting every character rather than every word.
Corrected code:
from tkinter import *
class DM_3_1:
def __init__(self):
root = Tk()
root.geometry('250x150')
root.title("DiscreteMaths_3_1")
usertext = StringVar()
Label_1 = Label(root, text="Input")
Label_2 = Label(root, text="Output")
inputField = Entry(root, textvariable = usertext)
outputField = Entry(root)
inputField.bind('<Return>', lambda _: printLine())
def printLine():
counter = {}
words=inputField.get().split()
for word in words:
counter[word] = counter.get(word, 0) + 1
Ans = counter[word] - 1
print(Ans, end=" ")
outputField.insert(END, str(Ans))
Label_1.grid(row = 0)
Label_2.grid(row = 1)
inputField.grid(row = 0, column = 1)
outputField.grid(row = 1, column = 1)
root.mainloop()
DM_3_1()
edit:
As Mike-SMT pointer out its easier to use .split
code edited to use .split
So my solutions to this kind of counter is to track each word and keep a list of all the words. Then keep a list of all the unique words. The count each time a unique word appears in the complete list.
I restructured your code a bit to conform a bit better with standards.
I rewrote your printLine method to keep track of all the words in a string and create a dictionary that contains a list of all the unique words and how many times they show up in the string.
When writing a class you will need to learn to use self. to convert standard variables into class attributes. Class attributes can be accessed from anywhere in the class including methods within the class. Using regular variables will likely cause problems as they are not available to methods after __init__ has completed.
take a look at the below code.
import tkinter as tk
class DM_3_1:
def __init__(self, parent):
self.root = parent
self.root.geometry('250x150')
self.root.title("DiscreteMaths_3_1")
Label_1 = tk.Label(self.root, text="Input")
Label_2 = tk.Label(self.root, text="Output")
Label_1.grid(row=0)
Label_2.grid(row=1)
self.inputField = tk.Entry(self.root)
self.outputField = tk.Entry(self.root)
self.inputField.grid(row=0, column=1)
self.outputField.grid(row=1, column=1)
self.inputField.bind('<Return>', self.printLine)
def printLine(self, Event):
word_list = []
counter = 0
unique_words_in_string = []
total_times_word_appears = {}
for word in self.inputField.get().split():
word_list.append(word)
if word not in unique_words_in_string:
unique_words_in_string.append(word)
for word in unique_words_in_string:
counter = 0
for other_word in word_list:
if word == other_word:
counter += 1
total_times_word_appears[word]=counter
self.outputField.delete(0, "end")
self.outputField.insert("end", total_times_word_appears)
if __name__ == "__main__":
root = tk.Tk()
DM_3_1(root)
root.mainloop()

Clearing Placed Labels in Tkinter

So I have a currency which is increasing (that system is working fine). The first part updates the label every 100 ms. I have another button which triggers the second function which is supposed to clear the labels from the first. It sets home_status equal to 0 which should in theory run Money.place_forget() to clear the code. I have tested each part individually and it works but when I put the clears inside the elif statement it doesn't. It does not give me any errors, it just simply doesn't do anything (it does print END OF UPDATE HOME so the elif is triggered).
Any suggestions?
def updatehome(self):
print("UPDATE HOME")
global buy_button, home_status, currency
MoneyLabel = Label(self, text = "Money: ")
MoneyLabel.place(x = 5, y = 70)
Money = Label(self, text=currency)
Money.place(x = 50, y = 70)
if (home_status == 1):
self.after(100, self.updatehome)
elif (home_status == 0):
print("END OF UPDATE HOME")
Money.place_forget()
MoneyLabel.place_forget()
def clearhome(self):
print("CLEAR HOME")
global home_status
home_status = 0
you are creating ten labels every second, all stacked on top of each other, but you are only deleting the very last label you create.

How to use .get() within a function. Python

from Tkinter import *
import random
def Factorer(a,b,c):
while True:
random_a1=random.randint(-10,10)
random_a2=random.randint(-10,10)
random_c1=random.randint(-10,10)
random_c2=random.randint(-10,10)
if random_a1==0 or random_a2 == 0 or random_c1 == 0 or random_c2 == 0:
pass
elif (random_a1*random_c2) + (random_a2*random_c1) == b and random_a1/random_c1 != random_a2/random_c2 and random_a1*random_a2==a and random_c1*random_c2==c:
break
print "y=(%dx+(%d))(%dx+(%d))" % (random_a1,random_c1,random_a2,random_c2)
root = Tk()
buttonSim1 = Button(root, text="Convert", command=lambda: Factorer(enterA.get(),enterB.get(),enterC.get()))
buttonSim1.grid(row=2, column=3)
enterA = Entry(root)
enterA.grid(row=1, column=1)
enterB = Entry(root)
enterB.grid(row=1, column=2)
enterC = Entry(root)
enterC.grid(row=1, column=3)
root.mainloop()
How can I get this code to run, every time I click the button It just crashes.
It works however if I remove the .get() and just insert numbers.
Thanks in advance
You are comparing strings to ints, you would need to cast a,b and c to int:
Tkinter.Button(root, text="Convert", command=lambda: Factorer(int(enterA.get()),int(enterB.get()),int(enterC.get())))
The root of the problem is that you're comparing strings to integers, so your infinite while loop never finishes. That is why the program hands and has to be force-quit.
The best solution is to have your button call a function that gets the data, formats it to the proper value, and then calls the function to do the work. Trying to squeeze all of that into a lambda makes for a program that is hard to debug.
For example:
def on_button_click():
a = int(enterA.get())
b = int(enterB.get())
c = int(enterC.get())
result = Factorer(a,b,c)
print(result)
Tkinter.Button(..., command=on_button_click)
By using a separate function, it gives you the opportunity to add print statements or pdb breakpoints so you can examine the data as it is running. It also makes it easier to add try/catch blocks to handle the case where the user didn't enter valid numbers.

Categories

Resources