Why does Tk().after only execute once and how to fix it? - python

Why does Tk().after only execute once and how to fix it?
I tried to rerun .after() within tick(), but if I uncomment the line, the windows os not shown at all.
Using Windows 8.1 and Python 3
import tkinter as tk
def tick():
matrix_size = board.get_matrix_size()
alive_neighbours = [[None] * matrix_size for i in range(matrix_size)]
for row in range(matrix_size):
for col in range(matrix_size):
alive_neighbours[row][col] = get_alive_neighbours(row, col)
for row in range(matrix_size):
for col in range(matrix_size):
if alive_neighbours[row][col] == 3:
board.create_cell(row, col)
elif alive_neighbours[row][col] < 2:
board.kill_cell(row, col)
elif alive_neighbours[row][col] == 2 and board.get_cell_state(row, col) == "alive":
continue
elif alive_neighbours[row][col] > 3:
board.kill_cell(row, col)
# root.after(1000, tick())
def get_alive_neighbours(row, col):
alive_neighbour_count = 0
for relative_row in range(-1, 2): # Top cells
for relative_col in range(-1, 2):
if relative_row == 0 and relative_col == 0:
continue # Do not current cell as neighbour
else:
cell_state = board.get_cell_state(row + relative_row, col + relative_col)
if cell_state == "alive":
alive_neighbour_count += 1
return alive_neighbour_count
class GameBoard(tk.Frame):
matrix_size = 10
grid_size = 32
matrix = []
alive_color = "white"
dead_color = "grey"
def __init__(self, master=None):
self.matrix = [[None] * self.matrix_size for i in range(self.matrix_size)]
canvas_width = self.matrix_size * self.grid_size
canvas_height = self.matrix_size * self.grid_size
tk.Frame.__init__(self, master)
self.canvas = tk.Canvas(self, width=canvas_width, height=canvas_height)
self.draw_board()
self.initialize_cells()
self.canvas.grid()
def draw_board(self):
color = "white"
for row in range(len(self.matrix)):
for col in range(len(self.matrix[row])):
# color = "white" if (col+row)%2==0 else "lightgrey"
color = "lightgrey"
x0 = (col * self.grid_size + 2) # Why +2?
y0 = (row * self.grid_size + 2) # Why +2
x1 = x0 + self.grid_size
y1 = y0 + self.grid_size
id = self.canvas.create_rectangle(x0, y0, x1, y1, fill=color, width=0)
self.matrix[row][col] = id
def create_cell(self, row, column):
grid_item = self.matrix[row][column]
self.canvas.itemconfigure(grid_item, fill=self.alive_color)
def kill_cell(self, row, column):
grid_item = self.matrix[row][column]
self.canvas.itemconfigure(grid_item, fill=self.dead_color)
def get_cell_state(self, row, column):
try:
grid_item = self.matrix[row][column]
except IndexError:
return "dead"
else:
fill_color = self.canvas.itemcget(grid_item, "fill")
if fill_color == "white":
return "alive"
else:
return "dead"
def get_matrix(self):
return self.matrix
def get_matrix_size(self):
return self.matrix_size
def initialize_cells(self):
self.create_cell(1, 1)
self.create_cell(1, 2)
self.create_cell(2, 1)
self.grid()
if __name__ == "__main__":
root = tk.Tk()
root.resizable(0, 0)
board = GameBoard(master=root)
root.after(1000, tick())
root.mainloop()

The problem is that you're calling the methods when using them as a parameter, thus using the return value of the tick function (None).
Uncomment the line you mentioned and replace your after calls with
root.after(1000, tick)
Therefore, you're passing the tick function itself.

Related

A few minor problems with my tkinter sand simulation

I have made a sand simulation in tkinter. Only tkinter. No pygame, no matplotlib, none of that. It works great, it looks great, but nothing in life is perfect.
Minor Problem #1: When I hold down and let the sand fall, the column where the cursor is stays the same color. I haven't been able to track this down yet.
Minor Problem #2: When I create happy little piles of sand, the sides seem to build up from the bottom rather that fall from the top. I suspect this is a rendering issue, but I also haven't found the reason for this.
That's all the problems I've noticed, but if you see any others feel free to let me know.
Code:
from tkinter import *
from random import choice, random
from copy import deepcopy
from colorsys import hsv_to_rgb
CELLSIZE = 30
AIR = 0
WALL = 1
SAND = 2
BG = '#cef'
SANDCOLOR = (45, 45, 86)
WALLCOLOR = (224, 37, 34)
TARGETFPS = 100
def randomColor(h, s, v):
h, s, v = (h / 360), s / 100, v / 100
s += (random() - 0.5) * 0.1
v += (random() - 0.5) * 0.1
if s < 0: s = 0
if s > 1: s = 1
if v < 0: v = 0
if v > 1: v = 1
r, g, b = [round(i * 255) for i in hsv_to_rgb(h, s, v)]
return '#%02x%02x%02x' % (r, g, b)
class App:
def __init__(self):
global WIDTH, HEIGHT, SANDCOLOR, WALLCOLOR
self.master = Tk()
self.master.title('Sand Simulation')
self.master.resizable(0, 0)
self.master.attributes('-fullscreen', True)
WIDTH = self.master.winfo_screenwidth() // CELLSIZE
HEIGHT = self.master.winfo_screenheight() // CELLSIZE
Width, Height = WIDTH * CELLSIZE, HEIGHT * CELLSIZE
self.canvas = Canvas(self.master, width=Width, height=Height, bg=BG, highlightthickness=0)
self.canvas.pack()
self.map = [[AIR] * WIDTH for i in range(HEIGHT)]
self.colors = [[BG] * WIDTH for i in range(HEIGHT)]
self.positions = []
for x in range(WIDTH):
for y in range(HEIGHT):
self.positions.append([x, y])
self.positions.reverse()
self.dragging, self.dragX, self.dragY = False, 0, 0
self.canvas.bind('<Button-1>', self.mouseDown)
self.canvas.bind('<B1-Motion>', self.mouseDrag)
self.canvas.bind('<ButtonRelease-1>', self.mouseUp)
## self.images = [PhotoImage(file='images/sandButton.png'), PhotoImage(file='images/sandButtonActivated.png'),
## PhotoImage(file='images/wallButton.png'), PhotoImage(file='images/wallButtonActivated.png')]
self.images = [PhotoImage().blank(), PhotoImage().blank(), PhotoImage().blank(), PhotoImage().blank()]
self.sandButton = self.canvas.create_image(125, 125, anchor='center', image=self.images[1])
self.wallButton = self.canvas.create_image(125, 325, anchor='center', image=self.images[2])
self.drawingMode = 'SAND'
self.master.after(round(1 / TARGETFPS * 1000), self.frame)
self.master.mainloop()
def swapBlocks(self, x1, y1, x2, y2):
block1 = self.map[y1][x1]
color1 = self.colors[y1][x1]
self.map[y1][x1] = self.map[y2][x2]
self.colors[y1][x1] = self.colors[y2][x2]
self.map[y2][x2] = block1
self.colors[y2][x2] = color1
def mouseDown(self, event):
if 50 < event.x < 200 and 50 < event.y < 200:
self.drawingMode = 'SAND'
self.canvas.itemconfig(self.sandButton, image=self.images[1])
self.canvas.itemconfig(self.wallButton, image=self.images[2])
elif 50 < event.x < 200 and 250 < event.y < 400:
self.drawingMode = 'WALL'
self.canvas.itemconfig(self.sandButton, image=self.images[0])
self.canvas.itemconfig(self.wallButton, image=self.images[3])
else:
self.dragging = True
self.dragX = event.x // CELLSIZE
self.dragY = event.y // CELLSIZE
if self.dragX > WIDTH - 1: self.dragX = WIDTH - 1
if self.dragX < 0: self.dragX = 0
if self.dragY > HEIGHT - 1: self.dragY = HEIGHT - 1
if self.dragY < 0: self.dragY = 0
def mouseDrag(self, event):
self.dragX = event.x // CELLSIZE
self.dragY = event.y // CELLSIZE
if self.dragX > WIDTH - 1: self.dragX = WIDTH - 1
if self.dragX < 0: self.dragX = 0
if self.dragY > HEIGHT - 1: self.dragY = HEIGHT - 1
if self.dragY < 0: self.dragY = 0
def mouseUp(self, event):
self.dragging = False
def updateParticles(self):
if self.dragging:
color = choice(['red', 'white', 'blue'])
if self.drawingMode == 'SAND':
self.map[self.dragY][self.dragX] = SAND
self.colors[self.dragY][self.dragX] = randomColor(SANDCOLOR[0], SANDCOLOR[1], SANDCOLOR[2])
elif self.drawingMode == 'WALL':
self.map[self.dragY][self.dragX] = WALL
self.colors[self.dragY][self.dragX] = randomColor(WALLCOLOR[0], WALLCOLOR[1], WALLCOLOR[2])
for block in self.positions:
x, y = block
block = self.map[y][x]
if block == SAND:
if y == HEIGHT - 1:
below = WALL
else:
below = self.map[y + 1][x]
if below == AIR:
self.swapBlocks(x, y, x, y + 1)
else:
left, right, belowLeft, belowRight = AIR, AIR, AIR, AIR
if y == HEIGHT - 1:
belowLeft, belowRight = WALL, WALL
else:
if x == 0:
belowLeft = WALL
left = WALL
else:
belowLeft = self.map[y + 1][x - 1]
left = self.map[y][x - 1]
if x == WIDTH - 1:
belowRight = WALL
right = WALL
else:
belowRight = self.map[y + 1][x + 1]
right = self.map[y][x + 1]
if belowLeft == AIR and belowRight == AIR:
if choice([True, False]):
if left == AIR:
self.swapBlocks(x, y, x - 1, y + 1)
else:
if right == AIR:
self.swapBlocks(x, y, x + 1, y + 1)
else:
if belowLeft == AIR and left == AIR:
self.swapBlocks(x, y, x - 1, y + 1)
if belowRight == AIR and right == AIR:
self.swapBlocks(x, y, x + 1, y + 1)
def renderMap(self, previousMap):
for block in self.positions:
x, y = block
previousBlock = previousMap[y][x]
currentBlock = self.map[y][x]
x1, y1 = x * CELLSIZE, y * CELLSIZE
x2, y2 = x1 + CELLSIZE, y1 + CELLSIZE
if previousBlock == AIR and currentBlock != AIR:
if currentBlock == WALL: color = self.colors[y][x]
if currentBlock == SAND: color = self.colors[y][x]
rect = self.canvas.create_rectangle(x1, y1, x2, y2, outline='', fill=color)
self.canvas.tag_lower(rect)
if previousBlock != AIR and currentBlock == AIR:
blockAtPosition = self.canvas.find_enclosed(x1, y1, x2, y2)
self.canvas.delete(blockAtPosition)
if previousBlock != AIR and currentBlock != AIR and previousBlock != currentBlock:
blockAtPosition = self.canvas.find_enclosed(x1, y1, x2, y2)
self.canvas.delete(blockAtPosition)
if currentBlock == WALL: color = self.colors[y][x]
if currentBlock == SAND: color = self.colors[y][x]
rect = self.canvas.create_rectangle(x1, y1, x2, y2, outline='', fill=color)
self.canvas.tag_lower(rect)
self.canvas.update()
def frame(self):
previousMap = deepcopy(self.map)
self.updateParticles()
self.renderMap(previousMap)
self.master.after(round(1 / TARGETFPS * 1000), self.frame)
def main():
app = App()
if __name__ == '__main__':
main()
Please help me fix any bugs, glitches, etc...
Problem #1 is caused by the fact that your rendering function doesn't create a new rectangle if both the old and new block types are sand. As the sand falls in a column, the first bit of sand causes rectangles to be created with its color, and because another bit of sand always falls in its place on the next frame, it just stays that color. You could compare old and new colors to see which blocks need refreshing, or store which blocks changed.
Problem #2 is related to the order of your positions. You're inserting [x,y] positions into the positions array from the left to the right, one column at a time, each column from top to bottom, and then reversing the result, which gives you an ordering of bottom to top per column with the columns from right to left. So when you iterate through the positions, you're essentially sweeping from right to left as you process the blocks, so any block that falls to the left is going to be reprocessed in the same update, and as it keeps falling to the left it will keep being reprocessed, until it settles, and all of that happened in one frame. So particles will seem to fall to the left instantly.
I bet you don't have this problem with particles falling to the right, which is why on the right side of your image you have some diagonal bands of color: problem #1 is happening there too any time two sand blocks fall to the right in sequence.
You can fix problem #2 by reordering your width and height loops when you set up self.positions. Since blocks always move down one row when they are moved, iterating from bottom to top will always update each particle only once. If you ever introduce anything that makes particles move up, you'll need a better solution.

PHYTON: _tkinter.TclError: invalid command name ".!canvas"

My 9 year-old son is studying Python and he faced below problem in his code (bubble breaker game):
Traceback (most recent call last):
File "M:\Yandex disk\YandexDisk\ДЕТКИ\python modules\Bubble Blaster.py", line 55, in <module>
move_bubbles()
File "M:\Yandex disk\YandexDisk\ДЕТКИ\python modules\Bubble Blaster.py", line 48, in
move_bubbles
c.move(bub_id[i], -bub_speed[i], 0)
File "C:\Users\User\AppData\Local\Programs\Python\Python310\lib\tkinter\__init__.py", line
2949, in move
self.tk.call((self._w, 'move') + args)
_tkinter.TclError: invalid command name ".!canvas"*
I've rechecked the whole code twice but can't find any mistake.
Here is the whole code:
from tkinter import *
HEIGHT = 1080
WIDTH = 1920
window = Tk()
window.title('Bubble Blaster')
c = Canvas(window, width=WIDTH, height=HEIGHT, bg='darkblue')
c.pack()
ship_id = c.create_polygon(5, 5, 5, 25, 30, 15, fill='red')
ship_id2 = c.create_oval(0, 0, 30, 30, outline='red')
SHIP_R = 15
MID_X = WIDTH / 2
MID_Y = HEIGHT / 2
c.move(ship_id, MID_X, MID_Y)
c.move(ship_id2, MID_X, MID_Y)
SHIP_SPD = 10
def move_ship(event):
if event.keysym == 'Up':
c.move(ship_id, 0, -SHIP_SPD)
c.move(ship_id2, 0, -SHIP_SPD)
elif event.keysym == 'Down':
c.move(ship_id, 0, SHIP_SPD)
c.move(ship_id2, 0, SHIP_SPD)
elif event.keysym == 'Left':
c.move(ship_id, -SHIP_SPD, 0)
c.move(ship_id2, -SHIP_SPD, 0)
elif event.keysym == 'Right':
c.move(ship_id, SHIP_SPD, 0)
c.move(ship_id2, SHIP_SPD, 0)
c.bind_all('<Key>', move_ship)
from random import randint
bub_id = list()
bub_r = list()
bub_speed = list()
MIN_BUB_R = 10
MAX_BUB_R = 30
MAX_BUB_SPD = 10
GAP = 100
def create_bubble():
x = WIDTH + GAP
y = randint(0, HEIGHT)
r = randint(MIN_BUB_R, MAX_BUB_R)
id1 = c.create_oval(x - r, y - r, x + r, y + r, outline='white')
bub_id.append(id1)
bub_r.append(r)
bub_speed.append(randint(1, MAX_BUB_SPD))
def move_bubbles():
for i in range(len(bub_id)):
c.move(bub_id[i], -bub_speed[i], 0)
from time import sleep, time
BUB_CHANCE = 10
#MAIN GAME LOOP
while True:
if randint(1, BUB_CHANCE) == 1:
create_bubble()
move_bubbles()
window.update()
sleep(0.01)
def get_coords(id_num):
pos = c.coords(id_num)
x = (pos[0] + pos[2])/2
y = (pos[1] + pos[3])/2
return x, y
def del_bubble(I):
del bub_r[I]
del bub_speed[I]
c.delete(bub_id[i])
del bub_id[I]
def clean_up_bubs():
for i in range(len(bub_id)-1, -1, -1):
x, y = get_coords(bub_id[I])
if x < -GAP:
del_bubble(I)
#MAIN GAME LOOP
while True:
if randint(1, BUB_CHANCE) == 1:
create_bubble()
move_bubbles()
clean_up_bubs()
window.update()
sleep(0.01)
from math import sqrt
def distance(id1, id2):
x1, y1 = get_coords(id1)
x2, y2 = get_coords(id2)
return sqrt((x2 - x1)**2 + (y2 - y1)**2)
def collision():
points = 0
for bub in range(len(bub_id)-1, -1, -1):
if distance(ship_id2, bub_id[bub]) < (SHIP_R + bub_r[bub]):
points += (bub_r[bub] + bub_speed[bub])
del_bubble(bub)
return points
score = 0
#MAIN GAME LOOP
while True:
if randint(1, BUB_CHANCE) == 1:
create_bubble()
move_bubbles()
clean_up_bubs()
score += collision()
print(score)
window.update()
sleep(0.01)
What is causing this error?
Major mistakes:
no main loop
from tkinter import *
root = Tk()
root.mainloop()
https://tkdocs.com/tutorial/eventloop.html
all loops inside the main loop are created with the .after method
https://tkdocs.com/shipman/universal.html
when the window is closed, while cycles continue to work, so an error pops up _tkinter.TclError: invalid command name ".!canvas"*
and little things
c.delete(bub_id[i]) The variable "i" is not defined in the function.
x, y = get_coords(bub_id[I])
if x < -GAP:
del_bubble(I)
in this function does not define a large "I".

tkinter image not loading, tclerror

I'll try to explain this to my best ability. Short story short, making a tic tac toe game for a school project. I have all the code necessary i think, but when i run my code, tkinter window opens no problem, but when i am to click in one of the squares where i can place a "x" or a "o" my image wont load into the grid.
When i click, this is one the errors that pops up:
line 2776, in _create
return self.tk.getint(self.tk.call(
_tkinter.TclError: unknown option "562.5"
If i was to guess, the error must be in both "def draw_o" and "def draw_x"
If you want to try the code here is the images to download: https://imgur.com/a/kKJPIop
If relevant image of assignment: https://imgur.com/a/3iu4MJy
from math import log
from tkinter import *
import numpy as np
size = 600
symbol_size = (size / 3 - size / 8) /2
symbol_thick = 50
symbol_x_color = '#EE4035'
symbol_o_color = '#0492CF'
green = '#7BC043'
class cell:
def __init__(self):
self.window = Tk()
self.window.title("Bondesjakk/Tic Tac Toe")
self.canvas = Canvas(self.window, width = size, height = size)
self.canvas.pack()
self.window.bind("<Button-1>", self.click)
self.initialize_board()
self.player_x_turns = True
self.board_status = np.zeros(shape=(3, 3) )
self.player_x_starts = True
self.reset_board = False
self.gameover = False
self.tie = False
self.x_wins = False
self.o_wins = False
self.x_score = False
self.o_score = False
self.tie_score = False
def mainloop(self):
self.window.mainloop()
def initialize_board(self):
for i in range(2):
self.canvas.create_line( (i + 1) * size / 3, 0, (i + 1) * size / 3, size)
for i in range(2):
self.canvas.create_line(0, (i + 1) * size / 3, size, (i + 1) * size / 3)
def play_again(self):
self.initialize_board()
self.player_x_starts = not self.player_x_starts
self.player_x_turns = self.player_x_starts
self.board_status = np.zeros(shape =(3, 3) )
def draw_o(self, logical_pos):
o_image = PhotoImage('book/pybook/image/o.gif')
logical_pos = np.array(logical_pos)
grid_pos = self.convert_logical_to_grid_pos(logical_pos)
self.canvas.create_image(grid_pos[0] - symbol_size, grid_pos[1] - symbol_size,
grid_pos[0] + symbol_size, grid_pos[1] + symbol_size, width = symbol_thick,
image = o_image)
def draw_x(self, logical_pos):
x_image = PhotoImage('book/pybook/image/x.gif')
grid_pos = self.convert_logical_to_grid_pos(logical_pos)
self.canvas.create_image(grid_pos[0] - symbol_size, grid_pos[1] - symbol_size,
grid_pos[0] + symbol_size, grid_pos[1] + symbol_size, width = symbol_thick,
image = x_image)
def display_gameover(self):
if self.x_wins:
self.x_score += 1
text = "Winner: Player 1(x)"
color = symbol_x_color
elif self.o_wins:
self.o_score += 1
text = "Winner: Player 2(o)"
color = symbol_o_color
else:
self.tie_score += 1
text = "It's a tie"
color = "gray"
self.canvas.delete("all")
self.canvas.create_text(size / 2, size / 3, font = "cmr 60 bold", fill = color, text = text)
score_text = 'Scores \n'
self.canvas.create_text(size / 2, 5 * size / 8, font = "cmr 40 bold", fill = green, text = score_text)
score_text = 'Player 1 (x) : ' + str(self.x_score) + '\n'
score_text = 'Player 2 (o) : ' + str(self.o_score) + '\n'
score_text = 'Tie : ' + str(self.tie_score) + '\n'
self.canvas.create_text(size / 2, 3 * size / 4, font = "cmr 30 bold", fill = green, text = score_text)
self.reset_board = True
score_text = "Click to play again \n"
self.canvas.create_text(size / 2, 15* size / 16, font = "cmr 20 bold", fill = "gray", text = score_text)
def convert_logical_to_grid_pos(self, logical_pos):
logical_pos = np.array(logical_pos, dtype = int)
return (size / 3) * logical_pos + size / 6
def convert_grid_to_logical_pos(self, grid_pos):
grid_pos = np.array(grid_pos)
return np.array(grid_pos // (size / 3), dtype = int)
def is_grid_occupied(self, logical_pos):
if self.board_status[logical_pos[0] ][logical_pos[1] ] == 0:
return False
else:
return True
def is_winner(self, player):
player = -1 if player == "x" else 1
#Three in a row
for i in range(3):
if self.board_status[i][1] == self.board_status[i][2] == player:
return True
if self.board_status[0][1] == self.board_status[2][i] == player:
return True
#Diagonals
if self.board_status[0][0] == self.board_status[1][1] == self.board_status[2][2] == player:
return True
if self.board_status[2][0] == self.board_status[1][1] == self.board_status[0][2] == player:
return True
return False
def is_tie(self):
r, c = np.where(self.board_status == 0)
tie = False
if len(r) == 0:
tie = True
return tie
def is_gameover(self):
self.x_wins = self.is_winner("x")
if not self.x_wins:
self.o_wins = self.is_winner("o")
if not self.o_wins:
self.tie = self.is_tie()
gameover = self.x.wins or self.o.wins or self.tie
if self.x_wins:
print("x wins!")
if self.o_wins:
print("o wins!")
if self.tie:
print("It's a tie!")
return gameover
def click(self, event):
grid_pos = [event.x, event.y]
logical_pos = self.convert_grid_to_logical_pos(grid_pos)
if not self.reset_board:
if self.player_x_turns:
if not self.is_grid_occupied(logical_pos):
self.draw_x(logical_pos)
self.board_status[logical_pos[0] ][logical_pos[1] ] = -1
self.player_x_turns = not self.player_x_turns
else:
if not self.is_grid_occupied(logical_pos):
self.draw_o(logical_pos)
self.board_status[logical_pos[0] ][logical_pos[1] ] = 1
self.player_x_turns = not self.player_x_turns
if self.gameover():
self.display_gameover()
else:
self.canvas.delete("all")
self.play_again()
self.reset_board = False
game_instance = cell()
game_instance.mainloop()
In create_image() you use x1, y1, x2, y2, width=... but it expects only position (x1,y1) as tuple (without x2,y2) and without width=...
x = grid_pos[0] - symbol_size
y = grid_pos[1] - symbol_size
self.canvas.create_image( (x, y), image=x_image)
EDIT:
You have other mistakes
In line if self.gameover() you have to remove ()
--
If you use PhotoImage('book/pybook/image/o.gif') then it treats it as string with image's data, not as path to file. You have to use file=
o_image = PhotoImage(file='book/pybook/image/o.gif')
--
But there is another problem. There is bug in PhotoImage which removes image from memory when it is assigned to local variable in function. You should assign to class variable using self.
self.o_image = PhotoImage(file='book/pybook/image/o.gif')
--
And here another problem. You load the same image again and again and you assign it to the same variable - so bug removes previous image from memory and it removes it from canvas.
You could load it only once in __init__ and later use all time the same self.o_image, self.x_image
def __init___(...):
self.o_image = PhotoImage(file='book/pybook/image/o.gif')
self.x_image = PhotoImage(file='book/pybook/image/x.gif')
Doc (effbot.org in archive.org): PhotoImage, Canvas

Canvas absolute and relative coordinates, delta pixels retrieval

In this test script I draw a square I may zoom in by using the mouse wheel.
If I right click inside a cell I get the right cell coordinates (not x and y, but column and row): this is exactly what I expect it to write to the console in the background.
If I, instead, move the canvas by pressing the mouse left button and dragging it somewhere else, the coordinates are not right any more.
Where do I get the delta x and delta y (or offsets) to give back the right info?
FYI:
1) get_pos() is the method that does the check and produces the result.
2) the following code has been tested on Ubuntu 16.10 (with the latest updates) running Python 3.5.2.
import tkinter as tk
import tkinter.ttk as ttk
class GriddedMazeCanvas(tk.Canvas):
def almost_centered(self, cols, rows):
width = int(self['width'])
height = int(self['height'])
cell_dim = self.settings['cell_dim']
rows = rows % height
cols = cols % width
w = cols * cell_dim
h = rows * cell_dim
if self.zoom < 0:
raise ValueError('zoom is negative:', self.zoom)
zoom = self.zoom
if self.drawn() and 1 != zoom:
w *= zoom
h *= zoom
h_shift = (width - w) // 2
v_shift = (height - h) // 2
return [h_shift, v_shift,
h_shift + w, v_shift + h]
def __init__(self, *args, **kwargs):
if 'settings' not in kwargs:
raise ValueError("'settings' not passed.")
settings = kwargs['settings']
del kwargs['settings']
super().__init__(*args, **kwargs)
self.config(highlightthickness=0)
self.settings = settings
self.bind_events()
def draw_maze(self, cols, rows):
self.cols = cols
self.rows = rows
if self.not_drawn():
self.cells = {}
self.cell_dim = self.settings['cell_dim']
self.border_thickness = self.settings['border_thickness']
self.zoom = 1
self.delete(tk.ALL)
maze, coords = self._draw_maze(cols, rows, fix=False)
lines = self._draw_grid(coords)
return maze, lines
def _draw_maze(self, cols, rows, fix=True):
data = self.settings
to_max = data['to_max']
border_thickness = data['border_thickness']
poligon_color = data['poligon_color']
poligon_border_color = data['poligon_border_color']
coords = self.almost_centered(cols, rows)
if fix:
# Fix for the disappearing NW borders
if to_max == cols:
coords[0] += 1
if to_max == rows:
coords[1] += 1
maze = self.create_rectangle(*coords,
fill=poligon_color,
outline=poligon_border_color,
width=border_thickness,
tag='maze')
return maze, coords
def _draw_grid(self, coords):
data = self.settings
poligon_border_color = data['poligon_border_color']
cell_dim = data['cell_dim']
if coords is None:
if self.not_drawn():
raise ValueError('The maze is still uninitialized.')
x1, y1, x2, y2 = self.almost_centered(self.cols, self.rows)
else:
x1, y1, x2, y2 = coords
zoom = self.zoom
if self.drawn() and 1 != zoom:
if self.zoom < 1:
self.zoom = zoom = 1
print('no zooming below 1.')
else:
cell_dim *= zoom
lines = []
for i, x in enumerate(range(x1, x2, cell_dim)):
line = self.create_line(x, y1, x, y2,
fill=poligon_border_color,
tags=('grid', 'grid_hl_{}'.format(i)))
lines.append(line)
for i, y in enumerate(range(y1, y2, cell_dim)):
line = self.create_line(x1, y, x2, y,
fill=poligon_border_color,
tags=('grid', 'grid_vl_{}'.format(i)))
lines.append(line)
return lines
def drawn(self):
return hasattr(self, 'cells')
def not_drawn(self):
return not self.drawn()
def bind_events(self):
self.bind('<Button-4>', self.onZoomIn)
self.bind('<Button-5>', self.onZoomOut)
self.bind('<ButtonPress-1>', self.onScrollStart)
self.bind('<B1-Motion>', self.onScrollMove)
self.tag_bind('maze', '<ButtonPress-3>', self.onMouseRight)
def onScrollStart(self, event):
print(event.x, event.y, self.canvasx(event.x), self.canvasy(event.y))
self.scan_mark(event.x, event.y)
def onMouseRight(self, event):
col, row = self.get_pos(event)
print('zoom:', self.zoom, ' col, row:', col, row)
def onScrollMove(self, event):
delta = event.x, event.y
self.scan_dragto(*delta, gain=1)
def onZoomIn(self, event):
if self.not_drawn():
return
max_zoom = 9
self.zoom += 1
if self.zoom > max_zoom:
print("Can't go beyond", max_zoom)
self.zoom = max_zoom
return
print('Zooming in.', event.num, event.x, event.y, self.zoom)
self.draw_maze(self.cols, self.rows)
def onZoomOut(self, event):
if self.not_drawn():
return
self.zoom -= 1
if self.zoom < 1:
print("Can't go below one.")
self.zoom = 1
return
print('Zooming out.', event.num, event.x, event.y, self.zoom)
self.draw_maze(self.cols, self.rows)
def get_pos(self, event):
x, y = event.x, event.y
cols, rows = self.cols, self.rows
cell_dim, zoom = self.cell_dim, self.zoom
x1, y1, x2, y2 = self.almost_centered(cols, rows)
print('x1, y1, x2, y2:', x1, y1, x2, y2,
' bbox:', self.bbox('maze'))
if not (x1 <= x <= x2 and y1 <= y <= y2):
print('Here we are out of bounds.')
return None, None
scale = zoom * cell_dim
col = (x - x1) // scale
row = (y - y1) // scale
return col, row
class CanvasButton(ttk.Button):
def freeze_origin(self):
if not hasattr(self, 'origin'):
canvas = self.canvas
self.origin = canvas.xview()[0], canvas.yview()[0]
def reset(self):
canvas = self.canvas
x, y = self.origin
canvas.yview_moveto(x)
canvas.xview_moveto(y)
def __init__(self, *args, **kwargs):
if 'canvas' not in kwargs:
raise ValueError("'canvas' not passed.")
canvas = kwargs['canvas']
del kwargs['canvas']
super().__init__(*args, **kwargs)
self.config(command=self.reset)
self.canvas = canvas
root = tk.Tk()
settings = {'cell_dim': 3,
'to_max': 200,
'border_thickness': 1,
'poligon_color': '#F7F37E',
'poligon_border_color': '#AC5D33'}
frame = ttk.Frame(root)
canvas = GriddedMazeCanvas(frame,
settings=settings,
width=640,
height=480)
button = CanvasButton(frame, text='Reset', canvas=canvas)
button.freeze_origin()
canvas.draw_maze(20, 10)
canvas.grid(row=0, column=0, sticky=tk.NSEW)
button.grid(row=1, column=0, sticky=tk.EW)
frame.rowconfigure(0, weight=1)
frame.grid()
root.mainloop()
Looking at a previously answered question, I learned how to find the delta x and delta y I was looking for.
So, the solution is:
def get_pos(self, event):
x, y = event.x, event.y
cols, rows = self.cols, self.rows
cell_dim, zoom = self.cell_dim, self.zoom
x1, y1, x2, y2 = self.almost_centered(cols, rows)
# the following line stores deltax and deltay into x0, y0
x0, y0 = int(self.canvasx(0)), int(self.canvasy(0))
# then it is trivial to compute the solution
xa, ya = x1 - x0, y1 - y0
xb, yb = x2 - x0, y2 - y0
if not (xa <= x <= xb and ya <= y <= yb):
print('Here we are out of bounds.')
return None, None
scale = zoom * cell_dim
col = (x - xa) // scale
row = (y - ya) // scale
return col, row

How do I import a gif in Tkinter?

So I have this maze code, and it's really cool but when you play you move around a red square. Is there a way that I can change this red square that moves around into an image, instead of where it says return "red" I want to put this personaje.gif file so a little person can appear moving through the maze.
this is my code:
import random
import Tkinter as tk
import sys
from PIL import Image, ImageTk
class Application(tk.Frame):
def __init__(self, width=600, height=600, size=600):
tk.Frame.__init__(self)
self.maze = Maze(width, height)
personaje = Image.open("personaje.gif")
photo = ImageTk.PhotoImage(personaje)
self.size = size
self.steps = 0
self.grid()
self.create_widgets()
self.draw_maze()
self.create_events()
def create_widgets(self):
width = self.maze.width * self.size
height = self.maze.height * self.size
self.canvas = tk.Canvas(self, width=width, height=height)
self.canvas.grid()
self.status = tk.Label(self)
self.status.grid()
def draw_maze(self):
for i, row in enumerate(self.maze.maze):
for j, col in enumerate(row):
x0 = j * self.size
y0 = i * self.size
x1 = x0 + self.size
y1 = y0 + self.size
color = self.get_color(x=j, y=i)
id = self.canvas.create_rectangle(x0, y0, x1, y1, width=0, fill=color)
if self.maze.start_cell == (j, i):
self.cell = id
self.canvas.tag_raise(self.cell) # bring to front
self.status.config(text='Movidas mínimas: %d' % self.maze.steps)
def create_events(self):
self.canvas.bind_all('<KeyPress-Up>', self.move_cell)
self.canvas.bind_all('<KeyPress-Down>', self.move_cell)
self.canvas.bind_all('<KeyPress-Left>', self.move_cell)
self.canvas.bind_all('<KeyPress-Right>', self.move_cell)
def move_cell(self, event):
if event.keysym == 'Up':
if self.check_move(0, -1):
self.canvas.move(self.cell, 0, -self.size)
self.steps += 1
if event.keysym == 'Down':
if self.check_move(0, 1):
self.canvas.move(self.cell, 0, self.size)
self.steps += 1
if event.keysym == 'Left':
if self.check_move(-1, 0):
self.canvas.move(self.cell, -self.size, 0)
self.steps += 1
if event.keysym == 'Right':
if self.check_move(1, 0):
self.canvas.move(self.cell, self.size, 0)
self.steps += 1
args = (self.steps, self.maze.steps)
self.status.config(text='Movimientos %d/%d' % args)
self.check_status()
def check_move(self, x, y):
x0, y0 = self.get_cell_coords()
x1 = x0 + x
y1 = y0 + y
return self.maze.maze[y1][x1] == 0
def get_cell_coords(self):
position = self.canvas.coords(self.cell)
x = int(position[0] / self.size)
y = int(position[1] / self.size)
return (x, y)
def check_status(self):
if self.maze.exit_cell == self.get_cell_coords():
args = (self.steps, self.maze.steps)
self.status.config(text='Resuelto en %d/%d movidas!' % args)
def get_color(self, x, y):
if self.maze.start_cell ==(x,y):
return "red"
if self.maze.exit_cell == (x, y):
return 'green'
if self.maze.maze[y][x] == 1:
return 'black'
class Maze(object):
def __init__(self, width=21, height=21, exit_cell=(1, 1)):
self.width = width
self.height = height
self.exit_cell = exit_cell
self.create()
def create(self):
self.maze = [[1] * self.width for _ in range(self.height)] # full of walls
self.start_cell = None
self.steps = None
self.recursion_depth = None
self._visited_cells = []
self._visit_cell(self.exit_cell)
def _visit_cell(self, cell, depth=0):
x, y = cell
self.maze[y][x] = 0 # remove wall
self._visited_cells.append(cell)
neighbors = self._get_neighbors(cell)
random.shuffle(neighbors)
for neighbor in neighbors:
if not neighbor in self._visited_cells:
self._remove_wall(cell, neighbor)
self._visit_cell(neighbor, depth+1)
self._update_start_cell(cell, depth)
def _get_neighbors(self, cell):
x, y = cell
neighbors = []
# Left
if x - 2 > 0:
neighbors.append((x-2, y))
# Right
if x + 2 < self.width:
neighbors.append((x+2, y))
# Up
if y - 2 > 0:
neighbors.append((x, y-2))
# Down
if y + 2 < self.height:
neighbors.append((x, y+2))
return neighbors
def _remove_wall(self, cell, neighbor):
x0, y0 = cell
x1, y1 = neighbor
# Vertical
if x0 == x1:
x = x0
y = (y0 + y1) / 2
# Horizontal
if y0 == y1:
x = (x0 + x1) / 2
y = y0
self.maze[y][x] = 0 # remove wall
def _update_start_cell(self, cell, depth):
if depth > self.recursion_depth:
self.recursion_depth = depth
self.start_cell = cell
self.steps = depth * 2 # wall + cell
def show(self, verbose=False):
MAP = {0: ' ', # path
1: '#', # wall
2: 'B', # exit
3: 'A', # start
}
x0, y0 = self.exit_cell
self.maze[y0][x0] = 2
x1, y1 = self.start_cell
self.maze[y1][x1] = 3
for row in self.maze:
print ' '.join([MAP[col] for col in row])
if verbose:
print "Steps from A to B:", self.steps
if __name__ == '__main__':
from optparse import OptionParser
parser = OptionParser(description="Random maze game")
parser.add_option('-W', '--width', type=int, default=43)
parser.add_option('-H', '--height', type=int, default=43)
parser.add_option('-s', '--size', type=int, default=11,
help="cell size")
args, _ = parser.parse_args()
for arg in ('width', 'height'):
if getattr(args, arg) % 2 == 0:
setattr(args, arg, getattr(args, arg) + 1)
print "Warning: %s must be odd, using %d instead" % (arg, getattr(args, arg))
sys.setrecursionlimit(5000)
app = Application(args.width, args.height, args.size)
app.master.title('Maze game')
app.mainloop()

Categories

Resources