Trying to build Risk style game in Tkinter - python-3.x

I have been trying to build my skills in Python and am trying to create a risk style game.
I am not far in to it at the moment as I am trying to get to grips with classes and Tkinter.
My first trial is to create a series of buttons to take the place of the different countries. I then want these buttons to update the amount of armies on the country when they are clicked.
So far I have been able to get the map to generate from the class I have created and the buttons are clickable. When a button is clicked it updates the amount of armies but always for the last button.
How do I get it so that the button I click updates and not the last one?
Have I gone about this in entirely the wrong way?
from tkinter import *
import random
class territory:
def __init__ (self, country, player = "1", current_armies = 0, x=0, y=0):
self.country = country
self.current_armies = current_armies
self.player = player
self.y = y
self.x = x
def get_armies(self):
print(self.country + " has " + str( self.current_armies)+ " armies.")
def add_armies (self, armies):
self.current_armies += armies
def roll_dice (self, dice=1):
rolls = []
for i in range(0, dice):
rolls.append(random.randint(1,6))
rolls.sort()
rolls.reverse()
print (self.country + " has rolled " + str(rolls))
return rolls
def owner(self):
print (self.country + " is owned by " + self.player)
def get_country(self):
print(country)
def button (self):
Button(window, text = territories[0].current_armies, width = 10, command = click1(territories, 0)).grid(row=y,column=x)
window = Tk()
def create_territories():
countries = ["UK", "GER", "SPA", "RUS"]
terr_pos = [[1,0],[2,0],[1,5],[4,1]]
sta_arm = [1,1,1,1]
terr = []
player = "1"
for i in range(len(countries)):
terr.append(territory(countries[i],player, sta_arm [i] , terr_pos[i][0],terr_pos[i][1]))
if player == "1":
player = "2"
else:
player = "1"
return terr
def click1(territory, i):
territory[i].current_armies += 1
build_board(territory)
def build_board(territories):
for i in range(0,4):
Button(window, text = territories[i].country+"\n"+str(territories[i].current_armies), width = 10, command = lambda: click1(territories, i)).grid(row=territories[i].y,column=territories[i].x)
territories = create_territories()
window.title ("Domination")
create_territories()
build_board(territories)
window.mainloop()

In your def button(self):... you are always referencing territories[0]:
Button(window, text=territories[0].current_armies,... command=click1(territories, 0)...
As such, you are always using the first territory as your reference, so you ought to initialize each territory with its index in territories[] so you can pass that into your Button constructor.
On your question of "entirely the wrong way," I'd personally send that question over to CodeReview, since that's more of their domain (we fix broken code, they address smelly code), though there is significant overlap. We do prefer one question per question, however, and "is this whole thing wrong?" is a little broad for StackOverflow.

Related

What can i do to print information from a nested dictionary that is send to class, and i need to show the result on the GUI

I am very stuck by trying to use tkinter, right now I am trying to print a receipt, my code works without the GUI, it prints the receipt sending the necessary information to a class, but now the professor wants us to have a GUI.
This is a one of the classes that I am using:
class Combo:
def __init__(self, dish, drink, order, price):
self.dish = dish
self.drink = drink
self.order = order
self.price = price
def combo_receipt(self, combo_number):
"""
This is for printing on the receipt the information of the combo
:return:
"""
return f"Combo {combo_number}.\nDish {self.dish}.\nDrink Order {self.drink}.\nFood Order {self.order}.\n" \
f"Price {self.price}.\n -------------"
This is how I am recollecting the information from a nested dictionary to send it to another function:
def receipt_print(customer_receipts, taxes):
"""
This function will start the process of printing the receipt of the customer
:param customer_receipts: receipts dictionary
:param taxes: the tax to the math operation
:return:
"""
for key, value in customer_receipts.items():
print(key)
customer_name = input("Which receipt do you want to print?")
if customer_name in customer_receipts:
loop_print = int(customer_receipts[customer_name]["n_orders"])
num_of_orders = 0
total = 0
for n in range(loop_print):
if f"id{num_of_orders}" in customer_receipts[customer_name]:
if "fries" in customer_receipts[customer_name][f"id{num_of_orders}"]:
fr = "fries"
order_type = "food"
id_num_of_orders = f"id{num_of_orders}"
cost = int(customer_receipts[customer_name][f"id{num_of_orders}"]["fries"]["price"])
total += cost
order_print(fr, customer_receipts, order_type, customer_name, id_num_of_orders)
num_of_orders += 1
elif "soda" in customer_receipts[customer_name][f"id{num_of_orders}"]:
sd = "soda"
order_type = "drink"
id_num_of_orders = f"id{num_of_orders}"
cost = int(customer_receipts[customer_name][f"id{num_of_orders}"][sd]["price"])
total += cost
order_print(sd, customer_receipts, order_type, customer_name, id_num_of_orders)
num_of_orders += 1
elif "combo_1" in customer_receipts[customer_name][f"id{num_of_orders}"]:
cb1 = "combo_1"
combo_num = "1"
id_num_of_orders = f"id{num_of_orders}"
cost = int(customer_receipts[customer_name][f"id{num_of_orders}"][cb1]["price"])
total += cost
combo_print(cb1, customer_receipts, combo_num, customer_name, id_num_of_orders)
num_of_orders += 1
elif "combo_2" in customer_receipts[customer_name][f"id{num_of_orders}"]:
cb2 = "combo_2"
combo_num = "2"
id_num_of_orders = f"id{num_of_orders}"
cost = int(customer_receipts[customer_name][f"id{num_of_orders}"][cb2]["price"])
total += cost
combo_print(cb2, customer_receipts, combo_num, customer_name, id_num_of_orders)
num_of_orders += 1
elif "pizza" in customer_receipts[customer_name][f"id{num_of_orders}"]:
pz = "pizza"
id_num_of_orders = f"id{num_of_orders}"
cost = int(customer_receipts[customer_name][f"id{num_of_orders}"][pz]["price"])
total += cost
dish_print(pz, customer_receipts, customer_name, id_num_of_orders)
num_of_orders += 1
else:
sp = "spaghetti"
id_num_of_orders = f"id{num_of_orders}"
cost = int(customer_receipts[customer_name][f"id{num_of_orders}"][sp]["price"])
total += cost
dish_print(sp, customer_receipts, customer_name, id_num_of_orders)
num_of_orders += 1
total_print(total, taxes)
else:
print("Wrong receipt.\nBack to the menu")
In this case, the function that will handle the combos that the customer order, is:
def combo_print(combo, customer_receipts, comb_num, customer_name, id_or):
"""
When this function is call, it will print the combo information
:param combo: a variable to know which combo is in the order
:param customer_receipts: receipts dictionary
:param comb_num: a variable to be sent to the class Order
:param customer_name: a variable with the name of the customer to read the receipt dictionary
:param id_or: a variable with the name of the customer to read the receipt dictionary
:return:
"""
combo_order = customer_receipts[customer_name][id_or][combo]["order"]
combo_price = customer_receipts[customer_name][id_or][combo]["price"]
combo_drink = customer_receipts[customer_name][id_or][combo]["drink"]
combo_dish = customer_receipts[customer_name][id_or][combo]["dish"]
fill_object = Combo(combo_dish, combo_drink,combo_order, combo_price)
print(fill_object.combo_receipt(comb_num))
And this will be calling the function def combo_receipt on the class class Combo:, so with this class and another three, one for Dishes, other one for Orders and the last one for the total, it will be printing the receipt for the customer.
This is what it prints:
Just for visualize, the nested directory looks like this:
receipts = {"jONH": {"n_orders": 1,
"id0": {"combo_2": {"dish": "Spaghetti Carbonara",
"drink": "Soda",
"order": "French Fries",
"price": "10", "id": "combo"}}},
"Josh": {"n_orders": 2,
"id0": {"combo_1": {"dish": "Personal Pizza",
"drink": "Soda",
"order": "French Fries",
"price": "8",
"id": "combo"}},
"id1": {"soda": {"order": "Soda",
"price": "2",
"id": "order"}
}
}
}
But as I already said, I need to use a GUI, but I know if there is a way to save the return value from the function def combo_receipt on the class class Combo: and send it to the GUI or what I can do to still be using the class and the GUI, cause it is requested.
I can't show you anything about my tries with this, cause I am really stuck with the GUI, so I am just watching video guides of tkinter or looking in google for information.
I will appreciate any advice, link or anything that help me to understand what I need to do
I have here an oop solution for the GUI where you can implement your functions. In the main file you create the app, mainFrame and Notebook with a tab.
In the second File you get the class with the init function for Entries/Labels/Buttons, you have to add elements you need. On the bottom you are able to implement your functions. Hope that helps you further with the GUI.
main.py:
import tkinter as tk
from tkinter import ttk
from abc import createUI0
# class MainFrame that inherits from ttk.Frame
class MainFrame(ttk.Frame): #ttk.Frame
def __init__(self, container): #init method
super().__init__(container)
# Create notebook
self.tabControl = ttk.Notebook(self)
tab1 = createUI0(self.tabControl)
# Create Tab from tab1 from file XYZ
self.tabControl.add(tab1, text ='XYZ')
self.tabControl.pack(expand = 1, fill ="both")
if __name__ == "__main__":
app = tk.Tk()
app.title('XZY')
app.geometry('670x580')
app.resizable(0, 0)
MainFrame(app).pack(side="top", fill="both", expand=True)
app.mainloop()
abc.py:
import tkinter as tk
from tkinter import ttk
from tkinter import *
class createUI0(ttk.Frame):
def __init__(self, container): #init method
super().__init__(container)
# field options
options = {'padx': 5, 'pady': 5}
# Titel Labels
self.title = ttk.Label(self, text='XXX', font=('Helvetica', 17, 'bold'))
self.title.grid(column=1, row=0, sticky=tk.W, **options)
# Item Detail Labels
self.itemDetail = ttk.Label(self, text='YYY:', font=('Helvetica', 16, 'bold'))
self.itemDetail.grid(column=0, row=5, sticky=tk.W, **options)
# XEntry, your variable to work with
self.X = tk.StringVar()
self.XEntry = ttk.Entry(self, width=23, textvariable=self.X)
self.XEntry.grid(column=1, row=11, sticky=tk.W, **options)
self.XEntry.focus()
# Button to start your function for example
self.button_create = ttk.Button(self, width = 15, text='use YourFunction', command=self.yourFunction)
self.button_create.grid(column=0, row=22, **options, sticky=tk.W)
def yourFunction(self):
#your function
pass

How to store in variables current row selection within tree widget to query a database using python

I wonder if it is possible to store in variables the contents from a tree widget row (when it is selected with the mouse) see picture. Basically I want to sync my tree with a database, every time when I insert or delete an element in my tree, my database needs to auto update.
With the insert part it is not a problem , because I have entry widgets, but I don't know how to manage the delete part. Therefore, I wonder if it is possible to do this with some cursor selection function.
I have been trying for a very long time to find a solution for this, I would really appreciate if someone can help me with some hints
Code:
import tkinter
from tkinter import ttk
class cards(tkinter.Frame):
def __init__(self, parent):
tkinter.Frame.__init__(self, parent)
self.parent=parent
self.parent.geometry("800x500")
self.initialize_user_interface()
def initialize_user_interface(self):
self.parent.title("cards")
self.parent.grid_rowconfigure(0,weight=1)
self.parent.grid_columnconfigure(0,weight=1)
self.parent.config(background="lavender")
self.Card_label = tkinter.Label(self.parent, text = "Card type:")
self.Card_entry = tkinter.Entry(self.parent)
self.Card_label.place(x=5,y=5)
self.Card_entry.place(x=70,y=5)
self.SN_label = tkinter.Label(self.parent, text = "SN:")
self.SN_entry = tkinter.Entry(self.parent)
self.SN_label.place(x=5,y=40)
self.SN_entry.place(x=70,y=40)
self.submit_button = tkinter.Button(self.parent, text = "Insert", command = self.insert_data)
self.submit_button.place(x=210,y=15)
self.exit_button = tkinter.Button(self.parent, text = "Exit", command = self.exit)
self.exit_button.place(x=270,y=15)
self.tree = ttk.Treeview( self.parent, columns=('Card Type', 'SN'))
self.tree.heading('#0', text='Nr.')
self.tree.heading('#1', text='Card Type')
self.tree.heading('#2', text='SN')
self.tree.column('#1', stretch=tkinter.YES)
self.tree.column('#2', stretch=tkinter.YES)
self.tree.column('#0', stretch=tkinter.YES)
self.tree.place(x=0,y=100)
self.treeview = self.tree
self.i = 1
def exit(self):
self.master.destroy()
def insert_data(self):
self.treeview.insert('', 'end', text=str(self.i), values=(self.Card_entry.get(), self.SN_entry.get()))
self.i = self.i + 1
def main():
root=tkinter.Tk()
d=cards(root)
root.mainloop()
if __name__=="__main__":
main()
You can use
for item in self.tree.selection():
print(self.tree.item(item, "text"))
print(self.tree.item(item, "values"))
#print(self.tree.item(item))
to see data from all selected rows - you can select more than one row.
You can use it in function assigned to button
or you can use bind() to assign function to mouse click on row.

Altering attributes in another class in Python

I have been trying to alter a list in class Inventory from class Pod, but I get an error that I am popping from an empty set. Is there anyway that I can pop from a list from an Inventory instance that I know is populated? Essentially, I am trying to transfer widgets from Inventory to Pod.
class Widget():
def __init__(self):
self.cost = 6
self.value = 9
class Inventory():
def __init__(self):
self.widgets_inv = []
self.cost_inv = 0
self.value_inv = 0
def buy_inv(self):
x = int(input("How many widgets to you want to add to inventory? "))
for i in range(0, x):
self.widgets_inv.append(Widget())
def get_inv(self):
print("You have " + str(len(self.widgets_inv)) + " widgets in inventory.")
def cost_of_inv(self):
cost_inv = len(self.widgets_inv) * Widget().cost
print("The current cost of your inventory is: " + cost_inv + " USD.")
def value_of_inv(self):
val_inv = len(self.widgets_inv) * Widget().value
print("The current value of your inventory is: " + val_inv + " USD.")
class Pod():
"""A pod is a grouping of several widgets. Widgets are sold in pods"""
def __init__(self):
self.pod = []
def creat_pod(self):
x = int(input("How many widgets would you like to place in this pod? "))
for i in range(0, x):
self.pod.append(Widget())
Inventory().widgets_inv.pop()
You should modify the creat_pod-method, so that you can handover the Inventory-object. This allows you to add widgets to the inventory-object before calling creat_pod-method:
def creat_pod(self, inventory):
x = int(input("How many widgets would you like to place in this pod? "))
for i in range(0, x):
self.pod.append(Widget())
inventory.widgets_inv.pop()
In your original code you create always a new Inventory-object, which has therefore and empty widget-list:
Inventory().widgets_inv.pop()

Python3: GUI later cannot display output properly?

I'm designing a GUI application that converts between celsius and fahrenheit. For now, there're primarily two problems that I'm not able to tackle:
1) When I enter an integer that needs to be converted based on the given conversion formula, the Label from tkinter cannot display the output properly. In fact, it shows something like this:
<conversionModel.Conversion object at 0x1057b11d0>
which made it really difficult for debug to a beginner like me.
2) There's a quitButton, thought which we can destroy() the GUI application. The problem is that when I close the GUI by clicking the red cross of the window, the Shell says:
_tkinter.TclError: can't invoke "destroy" command: application has been destroyed
I checked answers to other questions regarding the same problem, it turned out that it was because this GUI application was destroyed before closing. I had no idea how to address this particular problem.
Below are three pieces of code written in Model/View/Controller form:
The Model in conversionModel.py:
class Conversion:
"""
class Conversion is the Model for a celsius-fahrenheit conversion
application. It converts celsius into fahrenheit and fahrenheit into
celsius.
"""
def toCelsius(self, temp):
return (5 / 9) * (temp - 32)
def toFahrenheit(self, temp):
return ((9 / 5) * temp) + 32
The View in conversionView.py:
import tkinter
class MyFrame(tkinter.Frame):
def __init__(self, controller):
tkinter.Frame.__init__(self)
self.pack()
self.controller = controller
self.tempEntry = tkinter.Entry()
self.tempEntry.insert(0, "0")
self.tempEntry.pack({"side": "left"})
self.celsiusButton = tkinter.Button(self)
self.celsiusButton["text"] = "Celsius"
self.celsiusButton["command"] = self.controller.buttonToC
self.celsiusButton.pack({"side": "left"})
self.fahrenheitButton = tkinter.Button(self)
self.fahrenheitButton["text"] = "Fahrenheit"
self.fahrenheitButton["command"] = self.controller.buttonToF
self.fahrenheitButton.pack({"side": "left"})
self.labelForOutput = tkinter.Label(self)
self.labelForOutput["text"] = 0
self.labelForOutput.pack ({"side": "left"})
self.quitButton = tkinter.Button(self)
self.quitButton["text"] = "Quit"
self.quitButton["command"] = self.quit
self.quitButton.pack({"side": "left"})
The Controller in controller.py:
import tkinter
import conversionView
import conversionModel
class Controller:
def __init__(self):
root = tkinter.Tk()
self.model = conversionModel.Conversion()
self.view = conversionView.MyFrame(self)
self.value = float(self.view.tempEntry.get())
self.view.mainloop()
root.destroy()
def buttonToC(self):
self.model.toCelsius(self.value)
self.view.labelForOutput["text"] = str(self.model) + " °C"
def buttonToF(self):
self.model.toFahrenheit(self.value)
self.view.labelForOutput["text"] = str(self.model) + " °F"
if __name__ == "__main__":
c = Controller()
For #1, you need to read the tempEntry control each time you do a conversion, and capture the result of the conversion for printing. As it is now you only read the tempEntry control on __init__, and str(self.model) just prints out the name of the model object. This should work:
def buttonToC(self):
fahr = float(self.view.tempEntry.get())
temp = self.model.toCelsius(fahr)
self.view.labelForOutput["text"] = str(temp) + " °C"
def buttonToF(self):
celsius = float(self.view.tempEntry.get())
temp = self.model.toFahrenheit(celsius)
self.view.labelForOutput["text"] = str(temp) + " °F"
For #2, I'm not familiar enough with Tk yet to know why the Quit button works correctly but the red X destroys the windows before you get around to calling root.destroy, but this should work around it:
self.view.mainloop()
try:
root.destroy()
except tkinter.TclError:
pass
The Quit button needs the destroy, but the X button doesn't and throws an exception. This code just ignores the exception in the X button case.

Multithreading in Tkinter

I am new to Python and Tkinter but I was wondering if I could do Multithreading in Tkinter.
I have a Restaurant Simulation program and whenever it accepts an order, this is what should happen:
1) Timer that counts down and shows how many seconds are left before the order is done
2) While the Timer is counting, I want to create another instance of the Restaurant Simulation so that it could accept another order.
This is what I have tried:
from Tkinter import *
class food():
def __init__(self):
self.inventory = []
def cookOrder(self,type):
if type is 'AA': #Cook Barf
self.inventory[0]+=1
class Restaurant():
def Callback(self,root):
if tkMessageBox.askokcancel("Quit", "Do you really wish to quit?"):
root.destroy()
def MainMenu(self):
self.Main = Tk()
self.Main.grid()
Title = Label(self.Main,text = "Welcome to Resto!",font=("Bauhaus 93",20))
Title.grid(columnspan=6)
RestoMenu = Button(self.Main,text = "Order Food",command = lambda:self.Restaurant_Menu('AA'),font=("High Tower Text",12))
RestoMenu.grid(row=2,column=0)
self.Main.mainloop()
def Restaurant_Menu(self,type):
self.Main.destroy()
self.setCookTime(type)
def setCookTime(self,type):
self.MainMenu() #This is not working as I planned it to be
self.cookTimer = Tk()
self.timeLabel = Label(text="")
self.timeLabel.pack()
if type is "AA":
self.Tick(10,type)
self.cookTimer.mainloop()
self.cookTimer.wm_protocol ("WM_DELETE_WINDOW", self.Callback(self.cookTimer)) #Getting TCL Error
def Tick(self,time,type):
time -=1
if time ==-1:
food.cookOrder(type)
self.cook.destroy()
else:
self.timeLabel.configure(text="%d" %time)
self.cookTimer.after(1000, lambda: self.Tick(time,type))
Food = food()
Resto = Restaurant()
Resto.MainMenu()
Now, the problem with the code is, it is not creating another instance of the Restaurant while the countdown is going on. Any help?
Plus, how do you ask the user for the confirmation of exiting program when he clicks the exit button while the timer is ticking, using WM_DELETE_WINDOW?

Resources