Tkinter board game - python

I am making a board game with Tkinter. I create a grid:
def create_grid(self):
self.grid_frame = Frame(window)
self.grid_frame.grid(row=1, column=0)
self.grid_picture = PhotoImage(file="grid.PNG")
self.grid_label = Label(self.grid_frame, image=self.grid_picture)
self.grid_label.grid(row=0, column=0, columnspan=100, rowspan=10)
Then the pawns are placed based on their distance from start:
def green_grid_translation(self, green_position):
if green_position < 10:
self.green_grid_row = 9
self.green_grid_column = green_position*10+2
elif green_position < 20:
self.green_grid_row = 8
self.green_grid_column = 92 - (green_position - 10)*10
The pawns are placed on the same frame as the grid, the frame is created again with every move:
def position_interface(self):
self.grid_frame = Frame(window)
self.grid_frame.grid(row=1, column=0)
self.grid_picture = PhotoImage(file="grid.PNG")
self.grid_label = Label(self.grid_frame, image=self.grid_picture)
self.grid_label.grid(row=0, column=0, columnspan=100, rowspan=10)
self.green_picture = PhotoImage(file="green.png")
self.green_symbol = Label(self.grid_frame, image=self.green_picture)
self.green_symbol.grid(row=self.green_grid_row, column=self.green_grid_column)
self.blue_picture = PhotoImage(file="blue.png")
self.blue_symbol = Label(self.grid_frame, image=self.blue_picture)
self.blue_symbol.grid(row=self.blue_grid_row, column=self.blue_grid_column)
The following loops are used to make them go step by step:
for x in reversed(range(green_change[0])):
run_grid.green_grid_translation(green_change[1] - x)
run_grid.blue_grid_translation(blue_change[1])
run_grid.position_interface()
window.update()
sleep(1)
for x in reversed(range(blue_change[0])):
run_grid.green_grid_translation(green_change[1])
run_grid.blue_grid_translation(blue_change[1] - x)
run_grid.position_interface()
window.update()
sleep(1)
green_change[0] is the number of steps the pawn is supposed to move,
green_change[1] is its position on the grid
It works fine with a single pawn, but when there are two, it's like the number
of rows and columns changes and the pawns sometimes land in wrong positions:
Is there a way to fix it or do I need to take a completely different approach?

Your approach is wrong. There is plenty of stuff to improve, e.g the use of sleep in a GUI application is an absolute no-no.
But for the problem at hand, you simply use the wrong abstraction. Grids are for creating widgets in regular spaced layouts. But not for stacking/rearranging them. It CAN be done, but I would advise against it.
Use instead a canvas. This allows you to simply overlay graphical elements, and even move them around (smoothly if you are so inclined!).

Related

Tkinter application freezes with high number of entry/text fields

I'm trying to create a small program where I want to edit a big size table. See the example program below.
from tkinter import *
def table_generator(root):
size = 10
table_frame = Frame(root)
for i in range(size): # Rows
for j in range(size): # Columns
b = Text(table_frame, width=2, height=1)
b.grid(row=i, column=j)
table_frame.pack(side="left", anchor="nw")
if __name__ == '__main__':
application = Tk()
table_generator(application)
mainloop()
This works as I intended, but when I increase my size to 40, the program is slow to start and freezes if I try to move it. However, I want to increase this to around 200 at some point.
Does any of you have an alternative for this? I want to be able to enter a single character and change the background color in the end. Or some way of coding so that it does work with high numbers of Entry of Text fields?
Thanks

Make a program in which the user introduces the lenght value of the radius or side of a shape, then selects a shape, which is drawn with said value?

I need to make a program where the user enters the value that he wants the radius or side of the figure to have, and then chooses one figure from the possible ones from a list so that it is drawn with the previously entered value, and then the same with a second figure. For this I decided to use tkinter and turtle in python.
The problem is that when I want to store the value in a variable, I get this error: Entry.get() takes 1 positional argument but 2 were given"
Also, the code I use to trigger the select function and draw the circle, in this case, when that option is selected, ComboboxSelected doesn't seem to trigger anything. I don't know if I should put the option in another way inside the function. In this case, I imported turtle for drawing the figures, since it seemed less complex to me.
Obviously, the other figures still need to be programmed and the other list for the second figure, but I want it to work before continuing. All kind of advice is welcomed. Here is my code. Some parts are in spanish, but they are mostly the functions' and variable names and text displayed as titles, so I hope they dont become much of an obstacle.
from turtle import *
from tkinter import *
from tkinter import ttk
inicio = Tk()
inicio.geometry("600x500")
inicio.title("Dibujos de figuras")
texto = Entry(inicio, font = "Helvetica 15")
texto.place(relx=0, rely=0.1, relwidth=1, relheight=0.05)
m = None
radiolado = Label(inicio, text = "Radio o lado en cm")
radiolado.place(relx=0, rely=0.05)
def guardar_Valor():
global m
valor = Entry.get(1.0, "end-1c")
m = valor
def select(event):
if lista_1.get() == "Círculo":
c = Turtle()
c.circle(m)
figura_1 = Label(inicio, text = "Figura 1")
figura_1.place(relx=0.43,rely=0.25)
figura_2 = Label(inicio, text = "Figura 2")
figura_2.place(relx=0.43,rely=0.6)
lista_1 = ttk.Combobox(inicio, state="readonly",values=["Círculo", "Cuadrado", "Triángulo", "Pentágono", "Hexágono"])
lista_1.current(0)
lista_1.place(relx= 0.35,rely=0.3)
lista_1.bind("«ComboboxSelected»", select)
Boton1= Button(inicio, text = "Guardar", command = guardar_Valor)
Boton1.place(relx= 0.42,rely=0.18)
lista_2 = ttk.Combobox(inicio, state="readonly",values=["Círculo", "Cuadrado", "Triángulo", "Pentágono", "Hexágono"])
lista_2.current(1)
lista_2.place(relx= 0.35,rely=0.7)
inicio.mainloop()

How to make variables and functions that are attached on every single widget in for-loop in python?

I want to create 2D minecraft test game. Got page with textures and so on... And then, I realized I need to create 25*25 map!
In this code below you will see, that I created 5 functions and 5 labels in for-loop and if you are good at this language you will see I created block breaking animation. I'm going to care about holding animation after(if you know how to create holding animation please let me know)...
Code you can test:
from tkinter import *
tk=Tk()
tk.title('Minecraft 2D')
tk.config(bg='lightblue')
tk.resizable(False, False)
app_width=1500
app_height=750
screen_width=tk.winfo_screenwidth()
screen_height=tk.winfo_screenheight()
x=(screen_width/2)-(app_width/2)
y=(screen_height/2)-(app_height/2)
tk.geometry(f'{app_width}x{app_height}+{int(x)}+{int(y)}')
DestroyCounter=0
for i in range(10, 15):
def Destroy1():
global DestroyCounter
if DestroyCounter==0:
DestroyCounter=1
GrassBlockSprite.config(bg='gray30')
elif DestroyCounter==1:
DestroyCounter=2
GrassBlockSprite.config(bg='gray26')
elif DestroyCounter==2:
DestroyCounter=3
GrassBlockSprite.config(bg='gray22')
elif DestroyCounter==3:
DestroyCounter=4
GrassBlockSprite.config(bg='gray18')
elif DestroyCounter==4:
DestroyCounter=5
GrassBlockSprite.place_forget()
GrassBlockSprite=Canvas(tk, width=48, height=48, bg='brown', bd=0)
GrassBlockSprite.place(relx=((i+0.2)/31.5), rely=0.305)
GrassBlockSprite.bind('<Button-1>', lambda e:Destroy1())
tk.mainloop()
There just last block accepts animation, not other ones.
How to make this animation for other blocks? How to make variables and functions that will not overlap such as a loop in a thread? Do I need to create threads? But how to create them for every block in for loop? You can answer with just these 5 blocks in code. You can fix code too if you want.
Consider this as a replacement:
animation = ['gray30','gray26','gray22','gray18']
DestroyCounter=[]
GrassBlockSprite=[]
def Destroy(n):
if DestroyCounter[n] < len(animation):
GrassBlockSprite[n].config(bg=animation[DestroyCounter[n]])
else:
GrassBlockSprite[n].place_forget()
DestroyCounter[n] += 1
for i in range(5):
gbs=Canvas(tk, width=48, height=48, bg='brown', bd=0)
gbs.place(relx=((i+10.2)/31.5), rely=0.305)
gbs.bind('<Button-1>', lambda e, i=i:Destroy(i))
GrassBlockSprite.append(gbs)
DestroyCounter.append(0)

Python - Tkinter fix object position

Im currently testing around with python GUI and have made a script that takes 2 entered numbers from 2 textfields and upon a button press generates a block of labels (e.g. i enter 4 and 5 so it generates a 4x5 field of labels)
but now i want to do this: when i generate objects, i want to prevent them to
- move
- overlap
my current objects (buttons, textfields).
i can kind-of figure something for the overlapping, but every time i generate new stuff, everything moves around. Can i set a specific field in the grid to be "reserved" so that new stuff never goes in there?
this is my current attempt - as you can see, its not overlapping anymore, but if the snowflakes are generated, the textboxes and buttons still "jump" apart for a small distance
EDIT: the "jumps" are due to the font size of the added snowflakes - that still leaves my question on how i prevent this, as i dont want to be limited to small font sizes
from tkinter import *
wide = 0
deep = 0
entrytext = "test"
window = Tk()
window.title("test")
window.geometry('1000x1000')
ent = Entry(window)
ent.grid(column=0, row=1)
def GetClicked():
global wide
wide = ent.get()
wide = int(wide)
btn2 = Button(window, text="Width", command=GetClicked)
btn2.grid(column=0, row=2)
ent2 = Entry(window)
ent2.grid(column=0, row=3)
def GetClicked2():
global deep
deep = ent2.get()
deep = int(deep)
btn = Button(window, text="Depth", command=GetClicked2)
btn.grid(column=0, row=4)
def WingBut(column,row):
lbl = Label(window, text="T", font=("Wingdings", 15))
lbl.grid(column=column, row=row)
def clicked(wide,deep):
h = 0
j = 0
while h in range (deep):
i = 0
h += 1
while i in range(wide):
if i > 2 or j > 5:
WingBut(i,j)
i += 1
if i == wide:
j += 1
btn = Button(window, text="Buttonspam",font=("Arial", 10),command=lambda: clicked(wide,deep))
btn.grid(column=0, row=0)
window.mainloop()
the textboxes and buttons still "jump" apart for a small distance
This is due to the resulting size of the dynamically added labels (those labelled "T") being taller than the current row height for each row. Because the row size must increase to accommodate the new label, the other widgets in the same row are also resized so that the overall height for the row is consistent. That resize is causing the jumping effect.
One way to correct it would be to reduce the font size of the "T" labels. Try setting it to 10 and the problem should go away.
Another way to solve it would be to set the minsize for each row to be the height of the tallest widget in the row, e.g. the "T" label widget height.
for row in range(5):
window.rowconfigure(row, minsize=36)
You can add the above code before you call window.mainloop().
I selected 36 because this makes the rows a minimum of 36 pixels high, and this is sufficient on my system to display the "T" without causing the row to resize.
If you don't want to hardcode the minsize you could calculate it dynamically.
dummy = Label(window, text="T", font=("Wingdings", 20))
dummy.grid(row=0, column=0)
dummy.update_idletasks() # seems to be required to get rendered size
height = dummy.winfo_height()
dummy.grid_forget() # we don't want users seeing this widget
for row in range(5):
window.rowconfigure(row, minsize=height)
That's one way to do it. Possibly there is a better, more direct, way using the font itself, but you can research that if you're interested.

Bubblesort visualization in tkinter

I can't figure out how can I finish one simple program written in Python. Program basically generates array of ten random numbers and then sorts them using bubblesort algorithm. Whole shorting process should be shown on screen - such as this one
My current code is this:
import tkinter
import random
canvas = tkinter.Canvas(bg='white',width='800',height='400')
canvas.pack()
c = []
for i in range(0,10):
c=c+[random.randrange(10)]
print(c)
print('Zoradenie...', c)
def sort(c):
x=300
for i in range(0,10):
for j in range(0,len(c)-1-1):
if c[j+1]<c[j]:
c[j+1],c[j]=c[j],c[j+1]
canvas.create_text(300,80,text=c[j],fill='Red')
x+=25
canvas.update()
canvas.after(1000)
print(c)
return c
sort(c)
But I can't figure out how to show numbers on screen. Any ideas?
To display the digits on the canvas, you must create a text item for each digit. See the end of my code. The harder part is moving the digits. One way is to delete and recreate; the other is to move. I choose the latter.
The hardest part, perhaps, is the time delays. If one uses mainloop, one should use after rather than time.sleep (which blocks the looping) and not use for-loops for animation. The problem is that the function (here sort) that naturally contains for-loops must be broken into pieces whose joint operation may be hard to understand. If one is running just one function and does not care about user interaction (for instance, a pause button), one can use time.sleep and update. I have done so here to make what is going on clearer.
from random import randrange
from time import sleep
import tkinter as tk
root = tk.Tk()
canvas = tk.Canvas(root, bg='white', width='800', height='400')
canvas.pack()
ndigits = 10
digits = [randrange(10) for _ in range(ndigits)]
tdelta1, tdelta2 = .8, .2
xstart = 300
xdelta = 25
y = 80
def color(i, swap):
"Temporarily color digits i and i+i according to swap needed."
x = xstart + xdelta * i
dcolor = 'Red' if swap else 'green'
canvas.itemconfigure(items[i], fill=dcolor)
canvas.itemconfigure(items[i+1],fill=dcolor)
canvas.update()
sleep(tdelta1)
canvas.itemconfigure(items[i], fill='Black')
canvas.itemconfigure(items[i+1], fill='Black')
canvas.update()
sleep(tdelta2)
def swap(i):
digits[i], digits[i+1] = digits[i+1], digits[i]
canvas.move(items[i], xdelta, 0)
canvas.move(items[i+1], -xdelta, 0)
items[i], items[i+1] = items[i+1], items[i]
def bubsort():
"Sort digits and animate."
for stop in reversed(range(1, ndigits)):
# stop = index of position whose entry will be determined.
for i in range(stop):
swap_needed = digits[i] > digits[i+1]
color(i, swap_needed)
if swap_needed:
swap(i)
color(i, False)
# Create display items and pause.
items = [canvas.create_text(xstart + xdelta*i, y, text=str(digit))
for i, digit in enumerate(digits)]
canvas.update()
sleep(tdelta1)
bubsort()
This code makes it fairly easy to replace the text digit display with, for instance, a colored bar display. To develop this further, I would define a class of items combining int values and display items as attributes. There would them be only one array of combined items. With comparison methods defines, the array could be passed to any sort function.

Categories