How does the after method works in tkinter? - python

I am new at programing and I am trying to make a simple animation to better learn. I have just learned python (still nooby) and starting to learn tkinter.
I am trying to make an animation of the Conway's Game of Life because it has very simple principles and looks cool.
I have manage to actually make my code work but I really dont understand how.
The thing is that the method after I cannot understand how it works.
The part of the code that I dont understand is the method called start.
I really dont understand how "loop finished" can be printed before startloop function returns None (which it should be the same as saying the animation hasnt stop yet)
import tkinter as tk
width = 1400
height = 600
dist = 5
drawlines = False
celstate = set()
numcol = width//dist
numrow = height//dist
def getdeadcells(setcells):
global celstate
deadcells = set()
for cell in setcells:
i, j = cell
list = [(i-1, j-1), (i, j-1), (i+1, j-1),
(i-1, j), (i+1, j), (i-1, j+1), (i, j+1), (i+1, j+1)]
for cel in list:
if cel not in celstate:
deadcells.add(cel)
return deadcells
def getnewstate():
def neight(cell):
i, j = cell
count = 0
list = [(i-1, j-1), (i, j-1), (i+1, j-1),
(i-1, j), (i+1, j), (i-1, j+1), (i, j+1), (i+1, j+1)]
for cel in list:
if cel in celstate:
count +=1
return count
global celstate, numcol, numrow
alivecells = celstate.copy()
deadcells = getdeadcells(alivecells)
newstate = set()
for cell in alivecells:
neigh = neight(cell)
if neigh == 2 or neigh == 3:
newstate.add(cell)
for cell in deadcells:
neigh = neight(cell)
if neigh == 3:
newstate.add(cell)
if newstate == celstate:
return None
else:
celstate = newstate
if len(newstate) == 0:
return ""
else:
return newstate
def getcords(x, y):
col = x//dist
row = y//dist
return (col, row)
class GUI():
def __init__(self, master, width, height, dist):
master.geometry("{}x{}".format(width, height))
master.bind("<Key>", self.start)
self.master = master
self.width = width
self.height = height
self.dist = dist
self.canvas = tk.Canvas(master, width=width, height=height)
self.canvas.pack(expand=True)
self.drawlimits(dist)
def start(self, event):
if event.keycode == 32 or event.keycode == 13:
def startloop():
newstate = getnewstate()
if newstate == None:
return None
elif newstate == "":
self.canvas.delete("rect")
return None
else:
self.canvas.delete("rect")
self.fillrects(list(newstate))
self.master.after(100, startloop)
startloop()
print("loop finished")
def drawlimits(self, dist):
if self.width % dist == 0 and self.height % dist == 0:
self.canvas.bind("<B1-Motion>", self.drawcells)
self.canvas.bind("<ButtonRelease-1>", self.drawcells)
self.canvas.bind("<B3-Motion>", self.killcell)
self.canvas.bind("<ButtonRelease-3>", self.killcell)
if drawlines:
xsteps = self.width/dist
ysteps = self.height/dist
for num in range(int(xsteps-1)):
self.canvas.create_line((num+1)*dist, 0, (num+1)*dist, self.height)
for num in range(int(ysteps-1)):
self.canvas.create_line(0, (num+1)*dist, self.width, (num+1)*dist)
def drawcells(self, event):
cell = getcords(event.x, event.y)
if cell not in celstate:
self.fillrects([cell])
celstate.add(cell)
def killcell(self, event):
cell = getcords(event.x, event.y)
if cell in celstate:
celstate.remove(cell)
col, row = cell
tag = "{},{}".format(col, row)
obj.canvas.delete(tag)
def fillrects(self, cords):
for gcords in cords:
col, row = gcords
tag = "{},{}".format(col,row)
dist = self.dist
self.canvas.create_rectangle(col*dist, row*dist, (col+1)*dist, (row+1)*dist,
fill="black", tags=(tag, "rect"))
root = tk.Tk()
obj = GUI(root, width, height, dist)
root.mainloop()
The code works as following:
I only save the cells that are alive in the celstate set.
I then find the deadcells that could become alive and iterate over the dead and alive cells in the
If the celstate is the same as the previous or theres no alive cells: then the function getnewstate returns None.
In the start method I then call the function getnewstate and draw its content until celstate returns None (with the function startloop that calls itself with the after method).
I dont understand why "loop finished" can be printed if startloop hasnt stop yet.
Even though I dont understand this part the code still works as intended which just makes it more anoyingly mistirious for me.
Can anyone help clarify whats going on??
The dist variable represents the cell size in pixels
You can draw new cells using the left button of the mouse or erase existing ones using the right button. (The cool part is that you can do that while the animation is still going)
I'm sure the problem comes because I dont really understand how the mainloop works

The tkinter after method effectively sends a message to the mainloop() to run the callback function in n milliseconds. Your start function sends this message then prints "loop finished". It doesn't wait for the after callback to return before carrying on execution. 100 ms later it calls startloop() and recalculates and displays the new grid. If it did wait for the callback to return it would freeze the UI while it waited. The after function lets you run code after a delay but still have an active ui.
I've amended your start function to print "loop finished" instead of returning None on your exit parts of the code.
def start(self, event):
if event.keycode == 32 or event.keycode == 13:
def startloop():
newstate = getnewstate()
if newstate == None:
print("loop finished")
elif newstate == "":
self.canvas.delete("rect")
print("loop finished")
else:
self.canvas.delete("rect")
self.fillrects(list(newstate))
self.master.after(100, startloop)
startloop()
One problem you may have is that the game of life can reach stable conditions that return to the same sate every two cyles. Some shapes have even longer cycle periods.
HTH

Related

Python backtracking maze really slow

check the edit,
I wrote some code while watching The Coding Train on youtube.. the maze backtracking generator. The youtuber wrote the code in javascript and I tried to understand the code while writing in python. It seems he had to change the framerate because his program was so fast just to see the generating part. While mine after some 10ish squares it was already so slow.
It cant be an hardware problem, I've got an i5-4690K CPU and a good matching GPU, it must be something in the code! But I can't find what it is.
I rewatched the episodes so I could see what was wrong, but it seems I wrote everything just fine.
from tkinter import *
import math
import random
import time
# initialization canvas
root = Tk()
canvas = Canvas(root, width=400, height=400, bg="#333333")
canvas.pack()
# some global variables
w = 40;
cols = math.floor(int(canvas["width"])/w)
rows = math.floor(int(canvas["height"])/w)
grid = []
current = None
class Cell():
line_color = "#AAAAAA"
visited_color = "green"
visited = False
rectangle = None
def __init__(self, i, j):
self.i = i
self.j = j
self.wall = [True, True, True, True] # top , right, bottom, left
def __repr__(self):
return "({}, {})".format(self.i, self.j)
def draw_lines(self):
x = self.i*w
y = self.j*w
if self.visited :
self.rectangle = canvas.create_rectangle(x, y, x+w, y+w, fill="purple", outline="")
canvas.update()
if self.wall[0]:
canvas.create_line(x, y, x+w, y, fill=self.line_color)
else:
canvas.create_line(x, y, x+w, y, fill="purple")
if self.wall[1]:
canvas.create_line(x+w, y, x+w, y+w, fill=self.line_color)
else:
canvas.create_line(x+w, y, x+w, y+w, fill="purple")
if self.wall[2]:
canvas.create_line(x, y+w, x+w, y+w, fill=self.line_color)
else:
canvas.create_line(x, y+w, x+w, y+w, fill="purple")
if self.wall[3]:
canvas.create_line(x, y, x, y+w, fill=self.line_color)
else:
canvas.create_line(x, y, x, y+w, fill="purple")
def checkNeighbors(self):
neighbors = []
top = None
bottom = None
left = None
right = None
if index(self.i, self.j-1) != -1:
top = grid[index(self.i, self.j-1)]
if index(self.i, self.j+1) != -1:
bottom = grid[index(self.i, self.j+1)]
if index(self.i-1, self.j) != -1:
left = grid[index(self.i-1, self.j)]
if index(self.i+1, self.j) != -1:
right = grid[index(self.i+1, self.j)]
if top is not None and top.visited is False:
neighbors.append(top)
if right is not None and right.visited is False:
neighbors.append(right)
if bottom is not None and bottom.visited is False:
neighbors.append(bottom)
if left is not None and left.visited is False:
neighbors.append(left)
if len(neighbors) > 0:
r = random.randint(0, len(neighbors)-1)
return neighbors[r]
else:
return None
def removeWalls(a, b):
x = a.i - b.i
y = a.j - b.j
if x != 0:
if x == 1:
a.wall[3] = False
b.wall[1] = False
else:
a.wall[1] = False
b.wall[3] = False
if y != 0:
if y == 1:
a.wall[0] = False
b.wall[2] = False
else:
a.wall[2] = False
b.wall[0] = False
def index(i, j):
if j < 0 or j > rows - 1 or i < 0 or i > cols - 1:
return -1
return j + i * cols
def setup():
global current
for i in range(rows):
for j in range(cols):
cell = Cell(i, j)
grid.append(cell)
current = grid[0]
next_one = None
def draw():
global current
global next_one
stack = []
almost = False
while True:
current.visited = True
for cell in grid:
cell.draw_lines()
next_one = current.checkNeighbors()
if next_one:
stack.append(current)
removeWalls(current, next_one)
current = next_one
elif len(stack) > 0:
cell = stack.pop()
current = cell
for cell in grid:
print(cell.visited)
setup()
draw()
root.mainloop()
I'm sorry to put all the code, but I think all of it is relevant for what comes to performance, haven't put any useful comments, sorry I'm trying to become a better programmer and change that bad habit
BIG EDIT:
I tested to just draw my maze once I finished calculating it, and it takes less than a second, so I figure it has to be with the amount of widgets (lines) I'm creating..? How could I minimize the widgets so I could see the maze being created like I wanted to?
You draw lines even if they didn't change. Instead, draw only the changed cells.
Besides, you have endless loop. Even if stack contains no more elements, you don't stop. Here you should break the loop.
Here is an improvement:
def draw():
for cell in grid:
cell.draw_lines()
global current
global next_one
stack = []
almost = False
while True:
current.visited = True
next_one = current.checkNeighbors()
if next_one:
stack.append(current)
removeWalls(current, next_one)
current.draw_lines()
next_one.draw_lines()
current = next_one
elif len(stack) > 0:
cell = stack.pop()
current = cell
else:
break

How to use function in tkinter mainloop() [duplicate]

This question already has answers here:
Tkinter — executing functions over time
(2 answers)
Closed 4 years ago.
I decided to try out Python and it's been fun so far. However while messing around with tkinter I encountered a problem which I haven't been able to solve for hours. I've read some things and tried different stuff but nothing works.
I've got the code so far that I think the program should run fine. Except for the fact that I can't make it loop and thus update automatically.
So my question is: how can I call a function with tkinters loop options in an infite loop fashion?
Simple game of life:
I wrote basicly 2 classes. A Matrix which stores and handles the single cells
and the game itself which utilizes the matrix class through some game logic and basic user input.
First the game class as there is my loop problem:
from tkinter import *
from ButtonMatrix import *
class Conway:
def __init__(self, master, size = 20, cell_size = 2):
self.is_running = True
self.matrix = ButtonMatrix(master,size,cell_size)
self.matrix.randomize()
self.matrix.count_neighbours()
self.master = master
# playbutton sets boolean for running the program in a loop
self.playbutton = Button(master, text = str(self.is_running), command = self.stop)
self.playbutton.grid(row = 0 , column = size +1 )
#Test button to trigger the next generation manually. Works as itended.
self.next = Button(master, text="next", command = self.play)
self.next.grid(row = 1, column = size +1)
def play(self): # Calculates and sets the next generation. Intended to be used in a loop
if self.is_running:
self.apply_ruleset()
self.matrix.count_neighbours()
self.apply_colors()
def apply_ruleset(self):
#The ruleset of conways game of life. I wish i knew how to adress each element
#without using these two ugly loops all the time
size = len(self.matrix.cells)
for x in range (size):
for y in range (size):
if self.cell(x,y).is_alive():
if self.cell(x,y).neighbours < 2 or self.cell(x,y).neighbours > 3:
self.cell(x,y).toggle()
if not self.cell(x,y).is_alive() and self.cell(x,y).neighbours == 3:
self.cell(x,y).toggle()
def apply_colors(self): #Some flashy colors just for fun
size = len(self.matrix.cells)
for x in range (size):
for y in range (size):
if self.cell(x,y).is_alive():
if self.cell(x,y).neighbours < 2 or self.cell(x,y).neighbours > 3:
self.cell(x,y).button.configure(bg = "chartreuse3")
if not self.cell(x,y).is_alive() and self.cell(x,y).neighbours == 3:
self.cell(x,y).button.configure(bg = "lightgreen")
def cell(self,x,y):
return self.matrix.cell(x,y)
def start (self): #start and stop set the boolean for the loop. They work and switch the state properly
self.is_running = True
self.playbutton.configure(text=str(self.is_running), command =self.stop)
def stop (self):
self.is_running = False
self.playbutton.configure(text=str(self.is_running), command =self.start)
#Test program. I can't make the loop work. Manual update via next button works however
root = Tk()
conway = Conway(root)
root.after(1000, conway.play())
root.mainloop()
The Matrix (only for interested readers):
from tkinter import *
from random import randint
class Cell:
def __init__(self,master, cell_size = 1):
self.alive = False
self.neighbours = 0
# initializes a squares shaped button that fills the grid cell
self.frame = Frame(master, width= cell_size*16, height = cell_size*16)
self.button = Button(self.frame, text = self.neighbours, command = self.toggle, bg ="lightgray")
self.frame.grid_propagate(False)
self.frame.columnconfigure(0, weight=1)
self.frame.rowconfigure(0,weight=1)
self.button.grid(sticky="wens")
def is_alive(self):
return self.alive
def add_neighbour(self):
self.neighbours += 1
def toggle (self):
if self.is_alive() :
self.alive = False
self.button.configure( bg = "lightgray")
else:
self.alive = True
self.button.configure( bg = "green2")
class ButtonMatrix:
def __init__(self, master, size = 3, cell_size = 3):
self.master = master
self.size = size
self.cell_size = cell_size
self.cells = []
for x in range (self.size):
row = []
self.cells.append(row)
self.set_cells()
def cell(self, x, y):
return self.cells[x][y]
def set_cells(self):
for x in range (self.size):
for y in range (self.size):
self.cells[x] += [Cell(self.master, self.cell_size)]
self.cell(x,y).frame.grid(row=x,column=y)
def count_neighbours(self): # Checks 8 sourounding neighbours for their stats and sets a neighbour counter
for x in range(self.size):
for y in range(self.size):
self.cell(x,y).neighbours = 0
if y < self.size-1:
if self.cell(x,y+1).is_alive(): self.cell(x,y).add_neighbour() # Right
if x > 0 and self.cell(x-1,y+1).is_alive(): self.cell(x,y).add_neighbour() #Top Right
if x < self.size-1 and self.cell(x+1,y+1).is_alive(): self.cell(x,y).add_neighbour() #Bottom Right
if x > 0 and self.cell(x-1,y).is_alive(): self.cell(x,y).add_neighbour()# Top
if x < self.size-1 and self.cell(x+1,y).is_alive():self.cell(x,y).add_neighbour() #Bottom
if y > 0:
if self.cell(x,y-1).is_alive(): self.cell(x,y).add_neighbour() # Left
if x > 0 and self.cell(x-1,y-1).is_alive(): self.cell(x,y).add_neighbour() #Top Left
if x < self.size-1 and self.cell(x+1,y-1).is_alive(): self.cell(x,y).add_neighbour() #Bottom Left
self.cell(x,y).button.configure(text = self.cell(x,y).neighbours)
def randomize (self):
for x in range(self.size):
for y in range(self.size):
if self.cell(x,y).is_alive(): self.cell(x,y).toggle()
rando = randint(0,2)
if rando == 1: self.cell(x,y).toggle()
There are two problems with your code:
root.after(1000, conway.play())
First, you're not telling Tkinter to call conway.play after 1 second, you're calling conway.play() right now, which returns None, and then telling Tkinter to call None after 1 second. You want to pass the function, not call it:
root.after(1000, conway.play)
Meanwhile, after does not mean "call this function every 1000ms", it means "call this function once, after 1000ms, and then never again". The easy way around this is to just have the function ask to be called again in another 1000ms:
def play(self): # Calculates and sets the next generation. Itended to use in a loop
if self.is_running:
self.apply_ruleset()
self.matrix.count_neighbours()
self.apply_colors()
self.master.after(1000, self.play)
This is explained in the docs for after:
This method registers a callback function that will be called after a given number of milliseconds. Tkinter only guarantees that the callback will not be called earlier than that; if the system is busy, the actual delay may be much longer.
The callback is only called once for each call to this method. To keep calling the callback, you need to reregister the callback inside itself:
class App:
def __init__(self, master):
self.master = master
self.poll() # start polling
def poll(self):
... do something ...
self.master.after(100, self.poll)
(I'm assuming nobody's going to care if your timing drifts a little bit, so after an hour you might have 3549 steps or 3627 instead of 3600+/-1. If that's a problem. you have to get a bit more complicated.)

How to organize the code with GUI (with Tkinter) and logical components?

I'm doing a TicTacToe. So I have a class TicTacToe representing the state of the game (the grid, who own which cells, who is the next player to play) exposing methods to act on the game. It's the logical part.
I would like to put this in a GUI with Tkinter (imported as tk). I'm hesitating in several ways to do that:
create a TicTacToeUI class which inherits of both tk.Frame and TicTacToe;
create a TicTacToeUI class which inherits of tk.Frame and has a TicTacToe attribute;
create a TicTacToeUI class which inherits of tk.Frame which only defines graphical elements and aTicTacToeBindingUI which bind an instance of TicTacToe with an instance of TicTacToeUI.
Is this an idiomatic way to separate logical and GUI with Tkinter ?
Below is the code I did; it's commented in french but the name of the variables seem clear enough to understand the process. As you can see, I choose the 2ond solution. Any comment welcome.
from tkinter import *
class AlreadyTaken(Exception):
pass
class GameOver(Exception):
pass
class TicTacToe:
def __init__(self):
""" Built an object representing the state of the TicTacToe.
An instance of TicTacToe has two attributes:
self.next_player_to_play : indicate who is the next player (coding by 1 or 2).
self.grid (list of list): the grid 3x3 of the game; 0 is for
empty cells, 1 (resp. 2) is for the first (resp. second player).
"""
self.grid = [[0] * 3 for i in range(3)]
self.next_player_to_play = 1
self.game_over = False
def is_cell_free(self, i, j):
""" Return True if the cell (i, j) is empty, False orthewise.
Raises IndexError if i and j are not in [0,2]."""
if not ( 0 <= i <= 2 and 0 <= j <= 2):
raise IndexError('cell (%d, %d) index out of range'%(i, j))
return not self.grid[i][j]
def play(self, i, j):
""" The player self.next_player_to_play capture the cell (i, j),
if this cell is free. Raises AlreadyTaken exception if (i, j)
is not empty, or GameOver if the game is over.
Return 0 if the current player doesn't win, or the number of
the winning player."""
if self.game_over:
raise GameOver('the game is over, the winner is the player %d'
%self.next_player_to_play)
if not self.is_cell_free( i, j):
raise AlreadyTaken('cell (%d, %d) already taken by player %d'
%(i, j, self.grid[i][j]))
p = self.next_player_to_play
self.grid[i][j] = p
if self._is_victorious(p):
self.game_over = True
return p
# switch les deux joueurs
self.next_player_to_play = 3 - p
return 0
def _is_victorious(self, player):
""" Return True if the player `player` wins, i.e. if he succeed making a row, a column or a diagonal."""
g = self.grid
return (
# fill a column?
any(all(g[i][j] == player for i in range(3)) for j in range(3)) or
# fill a row ?
any(all(g[i][j] == player for j in range(3)) for i in range(3)) or
# fill a diagonal ?
all(g[i][i] == player for i in range(3)) or
# l'anti-diagonale ?
all(g[i][2-i] == player for i in range(3))
)
class TicTacToeUI(Frame):
def __init__(self,
master=None,
void_symb=' ',
player1_symb='X',
player2_symb='O',
tictactoe=None):
""" Build a frame drawing a TicTacToe game with 9 cells.
* `<situation>_symb`: indicates what symbol to use for a void cell, or owned by a player (1 or 2).
* `tictactoe`: by default (if `tictactoe` is None),a new game is created,
but we can use an existing TicTacToe instance instead."""
super().__init__(master, width=150, height=150)
if tictactoe is None:
tictactoe = TicTacToe()
self.tictactoe = tictactoe
symbols = [void_symb, player1_symb, player2_symb]
self._symbols = symbols
buttons_grid = []
self.buttons_grid = buttons_grid
Label(self, text='Super tictactoe!').grid(row=0, column=0, columnspan=3)
label_player_playing = Label(self, text='Player 1 is playing')
label_player_playing.grid(row=1, column=0, columnspan=3)
self.label_player_playing = label_player_playing
for i in range(3):
buttons_line = []
for j in range(3):
player = tictactoe.grid[i][j]
button = Button(
self,
text=symbols[player],
command=lambda i=i, j=j: self.play(i, j),
bg="green"
)
button.grid(row=i+2, column=j)
buttons_line.append(button)
buttons_grid.append(buttons_line)
Button(
self,
text='Start a new game',
command=self.reinit_game
).grid(column=0, row=5, columnspan=3)
def play(self, i, j):
try:
player = self.tictactoe.next_player_to_play
victorious_player = self.tictactoe.play(i, j)
self.buttons_grid[i][j]['text'] = self._symbols[player]
if victorious_player:
self.label_player_playing['text'] = 'Player %d wins!' % player
else:
self._update_label_player_playing()
except (AlreadyTaken, GameOver) as e:
pass
def _update_label_player_playing(self):
player = self.tictactoe.next_player_to_play
self.label_player_playing['text'] = 'Player %d is playing' % player
def _all_buttons_to_void(self):
for line in self.buttons_grid:
for b in line:
b['text'] = self._symbols[0]
def reinit_game(self):
self.tictactoe = TicTacToe()
self._all_buttons_to_void()
self._update_label_player_playing()
m = TicTacToeUI()
m.pack()
m.mainloop()

tkinter cant assign text variable

the problem was my input popup had to be a toplevel window so changing tk.Tk() to tk.Toplevel() makes it work fine ;)
#Josh Harrison
#3008088
from graphics import *
from random import randrange
import winsound, sys
#scale for size of squares
scale = 50
#setupBoard sets up the board with a randomly generated puzzle
def setupBoard(size, color):
board = [[[0, Rectangle(Point(scale*.05,scale*.05),Point(scale*.95,scale*.95))] for x in range(size)] for x in range(size)]
for x in range(size):
for y in range(size):
board[x][y][1] = Rectangle(Point(x*scale+scale*.05,y*scale+scale*.05),Point(x*scale+scale*.95,y*scale+scale*.95))
for i in range(1):
selectTile(board, Point(randrange(size)*scale,randrange(size)*scale), size, color)
return board
#selectTile does the action for selecting tiles
#set color to 0 for black and white and 1 for color rotation
def selectTile(board,point,size,color):
#sets value switch according to if colors are desired or not
if color == 1:
valueSwitch = colorSwitch
else:
valueSwitch = bwSwitch
x = int(point.getX()/scale)
y = int(point.getY()/scale)
#temp is made to preserve the selected tiles state
temp = board[x][y][0]
#swap all square values
#note try and except are also looped
for i in range(3):
for z in range(3):
try:
board[x-1+i][y-1+z][0] = valueSwitch(board[x-1+i][y-1+z][0])
except:
#overlap fix for x maxed
if x == size - 1 and y != size - 1:
board[0][y-1+z][0] = valueSwitch(board[0][y-1+z][0])
#overlap fix for y maxed
if y == size - 1 and x != size - 1:
board[x-1+i][0][0] = valueSwitch(board[x-1+i][0][0])
#overlap fix for bottom right corner
if x == size - 1 and y == size - 1:
board[0][0][0] = valueSwitch(board[0][0][0])
for a in range(2):
board[0][size-a-1][0] = valueSwitch(board[0][size-a-1][0])
board[size-a-1][0][0] = valueSwitch(board[size-a-1][0][0])
#give middle square initial value again
board[x][y][0] = temp
#updateBoard updates the squares to the right colour according to value
def updateBoard(board, size, count):
if count != 0:
winsound.Beep(333, 200)
for x in range(size):
for y in range(size):
if board[x][y][0] == 0:
board[x][y][1].setFill('white')
elif board[x][y][0] == 1:
board[x][y][1].setFill('yellow')
elif board[x][y][0] == 2:
board[x][y][1].setFill('green')
elif board[x][y][0] == 3:
board[x][y][1].setFill('blue')
elif board[x][y][0] == 4:
board[x][y][1].setFill('black')
#drawBoard draws the initial board
def drawBoard(size, board, win):
for x in range(size):
for y in range(size):
board[x][y][1].draw(win)
return
#checks to see if board is white(Winning condition)
def winGame(board, size):
#steps through all x and y values
for x in range(size):
for y in range(size):
if board[x][y][0] != 0:
return 0
#returns true if no black squares are found
return 1
#valueSwitch() just makes switching values easier by checking values and selecting the appropriate one
def colorSwitch(value):
if value == 4:
return 0
else:
return value + 1
#bwSwitch only selects from black and white value
def bwSwitch(value):
if value == 4:
return 0
else:
return 4
#winMessage() displaying a winning message graphic
def winMessage(size, scale, win):
gameMessage = Text(Point(size*scale/2,size*scale/2),"You have won logic!")
gameMessage.setSize(int(scale/4))
gameMessage.setTextColor('red')
gameMessage.draw(win)
#gameMenu() is a menu to select game size
def gameMenu():
win = GraphWin("Logic Menu", 400, 600)
win.setBackground('light blue')
board = [[[Text(Point(0,0),'bleh'),Rectangle(Point(0,0),Point(200,200))] for y in range(3)] for x in range(2)]
#Making and drawing the buttons ;)
for x in range(2):
for y in range(3):
board[x][y][1] = Rectangle(Point(x*200+200*.05,y*200+200*.05),Point(x*200+200*.95,y*200+200*.95))
board[x][y][1].draw(win)
board[0][0][0] = Text(board[0][0][1].getCenter(), 'Click for 5x5 puzzle')
board[1][0][0] = Text(board[1][0][1].getCenter(), 'Click for 7x7 puzzle')
board[0][1][0] = Text(board[0][1][1].getCenter(), 'Click for 9x9 puzzle')
board[1][1][0] = Text(board[1][1][1].getCenter(), 'Click for 12x12 puzzle')
board[0][2][0] = Text(board[0][2][1].getCenter(), 'Click to toggle colors')
board[1][2][0] = Text(board[1][2][1].getCenter(), 'Highscores!')
#drawing button options
for x in range(2):
for y in range(3):
board[x][y][0].draw(win)
#check to see what button is pressed
point = win.getMouse()
x = int(point.getX()/200)
y = int(point.getY()/200)
#colors is either 1 for colors or 0 for no colors
colors = 0
#turning colors on and off
#board[0][2][1] is the rectangle for colors
while y == 2:
if x == 0:
if colors == 0:
winsound.Beep(400, 200)
colors = 1
board[0][2][1].setFill('green')
else:
winsound.Beep(363, 200)
colors = 0
board[0][2][1].setFill('')
else:
winsound.Beep(400, 200)
board[1][2][1].setFill('red')
#board is just passed in for a smother button click effect not necessary for functionality
highscore_board(board)
point = win.getMouse()
x = int(point.getX()/200)
y = int(point.getY()/200)
board[x][y][1].setFill('red')
winsound.Beep(400, 200)
win.close()
if x == 0 and y == 0:
return 5 , colors
if x == 1 and y == 0:
return 7 , colors
if x == 0 and y == 1:
return 9 , colors
if x == 1 and y == 1:
return 12 , colors
return 5 , colors
#highscore() checks to see if player has highscore and outputs a highscore to a text file
def highscore(count):
#checks to see if highscore file exists
try:
scoreInfo = [line.strip() for line in open('highscore.txt')]
#remove all spacing
for i in range(scoreInfo.count('')):
scoreInfo.remove('')
scoreInfo[1]
scores = int(len(scoreInfo)/2)
newEntry = 0
#creates new highscore file is none exist
except:
win = GraphWin("Highscore!", 400, 200)
gameMessage = Text(Point(200,100),"Please input name: ")
gameMessage.setSize(int(scale/4))
gameMessage.setTextColor('red')
gameMessage.draw(win)
name=inputWin()
f = open('highscore.txt', 'w')
f.write(name)
f.write('\n'+str(count))
f.close()
gameMessage.setText(name+': '+str(count)+' - saved!')
time.sleep(1)
win.close()
return
#if there is a new highscore it is added at the beginning of the file
for i in range(scores):
if scores < 10 or count < int(scoreInfo[i*2+1]):
win = GraphWin("Highscore!", 400, 200)
gameMessage = Text(Point(200,100),"Please input name: ")
gameMessage.setSize(int(scale/4))
gameMessage.setTextColor('red')
gameMessage.draw(win)
name=inputWin()
f = open('highscore.txt', 'w')
#max 10 highscores 9 + new highscore
if scores >= 10:
scores = 9
for i in range(scores):
try:
if count < int(scoreInfo[i*2+1]) and not newEntry:
f.write(name)
f.write('\n'+str(count))
f.write('\n\n\n')
newEntry = 1
f.write(scoreInfo[i*2])
f.write('\n')
f.write(scoreInfo[i*2+1])
f.write('\n\n\n')
except:
pass
#if no entries have been added
#the new value is then added to the end
if newEntry == 0:
f.write(name)
f.write('\n'+str(count))
f.write('\n\n\n')
f.close()
gameMessage.setText(name+': '+str(count)+' - saved!')
time.sleep(1)
win.close()
break
pass
#board is just passed in for a smother button click effect not necessary for functionality
def highscore_board(board):
win = GraphWin("Highscores", 200, 500)
win.setBackground('light green')
try:
scoreInfo = [line.strip() for line in open('highscore.txt')]
#remove all spacing
for i in range(scoreInfo.count('')):
scoreInfo.remove('')
Text(Point(50,20),"Highscores:").draw(win)
for i in range(10):
Text(Point(10,45*i+60),str(i+1)+'. ').draw(win)
try:
Text(Point(60,45*i+60),scoreInfo[i*2]).draw(win)
Text(Point(170,45*i+60),scoreInfo[1+i*2]).draw(win)
except:
pass
except:
Text(Point(100,250),"no scores yet.").draw(win)
time.sleep(.05)
board[1][2][1].setFill('')
#prevent program crash if scoreboard is exited through os
try:
win.getMouse()
winsound.Beep(363, 200)
win.close()
except:
winsound.Beep(363, 200)
import tkinter as tk
def getString(ment,mGui):
global hsname
hsname = ment.get()
mGui.destroy()
mGui.quit()
def inputWin():
mGui = tk.Tk()
ment = tk.StringVar()
mGui.title('New Highscore!')
mEntry = tk.Entry(mGui,textvariable=ment).pack(side=tk.LEFT)
mbutton = tk.Button(mGui,text='OK',command=lambda:getString(ment,mGui),fg='red',bg='blue').pack(side=tk.RIGHT)
mGui.mainloop()
return hsname
this is the portion that wont work
import tkinter as tk
def getString(ment,mGui):
global hsname
hsname = ment.get()
mGui.destroy()
mGui.quit()
def inputWin():
mGui = tk.Tk()
ment = tk.StringVar()
mGui.title('New Highscore!')
mEntry = tk.Entry(mGui,textvariable=ment)
mEntry.pack(side=tk.LEFT)
mbutton = tk.Button(mGui,text='OK',command=lambda:getString(ment,mGui),fg='red',bg='blue')
mbutton.pack(side=tk.RIGHT)
mGui.mainloop()
return hsname
I'm just screwing around trying to make adding a highscore more visual for a game I made this code it works fine by itself but when I import it or even copy the whole code into a py file with other functions it just stops assigning ment any values I don't understand :/
any help is appreciated
this is the code that runs the game
#Josh Harrison
#3008088
from logic_game import *
def playGame():
option = gameMenu()
size = option[0]
color = option[1]
win = GraphWin("Logic Game", size*scale, size*scale)
win.setBackground('light pink')
board = setupBoard(size, color)
drawBoard(size, board, win)
countText = Text(Point(scale,scale/2),'moves: 0')
countText.setTextColor('red')
countText.draw(win)
count = 0
while not winGame(board, size):
updateBoard(board, size, count)
selectTile(board, win.getMouse(), size, color)
count += 1
countText.setText('moves: ' + str(count))
updateBoard(board, size, count)
winMessage(size, scale, win)
highscore(count)
#pauses the window and waits for click before continuing
win.getMouse()
#closes the window "win"
win.close()
playGame()
link for graphics.py
http://mcsp.wartburg.edu/zelle/python/graphics.py
I don't understand exactly what your problem is doing wrong, but I'm pretty sure I know what the problem is.
Most of your program is using some library named graphics to run a GUI. Then you're trying to use Tkinter to run another GUI in the same program.
I don't know what that graphics library that is, but unless it's either built on top of Tkinter, or specifically designed to work with Tkinter, this is unlikely to work. Both of them are going to try to be in charge of the one and only GUI for your program, handling all of the events from the user/windowing system, and so forth. One or both are going to fail.
In fact, even if graphics were built on top of Tkinter or designed to work together with it, calling mainloop on the Tkinter window is at best going to freeze up the rest of your GUI until you exit that mainloop, and at worst going to break the outer mainloop that the other GUI is relying on.
From what I can see from your other code, that graphics library seems to have enough features to do everything you were trying to do with Tkinter—create a new window, place some widgets on it, handle a button click. So, why not just use that?
Now that you've given us a link to the graphics library you're using… it looks like a thin wrapper around Tkinter. Which means you should be able to integrate them easily. You just have to create a new Toplevel instead of a root window (since graphics has already created a Tkinter root), and not call mainloop or quit (because you're already in a Tkinter main loop created by graphics).
Since you haven't given us an SSCCE that I can just run and hack on, I've built my own super-simple one around the first example in the graphics docs, which does what you were trying to do, and also shows how you can interact with the graphics window from the Tkinter code.
from graphics import *
import Tkinter as tk
def getString(ment,mGui):
global win
print(ment.get())
mGui.destroy()
win.close()
def inputWin():
global hsname
mGui = tk.Toplevel()
ment = tk.StringVar()
mGui.title('New Highscore!')
tk.Entry(mGui,textvariable=ment).pack(side=tk.LEFT)
tk.Button(mGui,text='OK',command=lambda:getString(ment,mGui),fg='red',bg='blue').pack(side=tk.RIGHT)
win.getMouse()
def main():
global win
win = GraphWin("My Circle", 100, 100)
c = Circle(Point(50,50), 10)
c.draw(win)
win.getMouse() # Pause to view result
inputWin()
main()
It would be better to refactor this to eliminate your global variables, either using an OO design (create a class so you can store things as instance attributes) or a functional design (pass values through closures or bake them in with lambda/partial, as you're already doing in your Button command), but I tried to follow the style you already set in your code rather than rewrite everything.

"after" looping indefinitely: never entering mainloop

This is my first post. I started coding when considering a career swap two months ago and am working on a Tetris clone. I've implemented most of the core features, but cannot get the game to refresh continually with an after loop.
I'm using Tkinter to produce my Gui and am trying out event oriented programming.
My understanding is that after(Time, Event) from Tkinter should schedule whatever the Event callback function is to occur after a delay specified by Time. I think that the code is supposed to continue executing subsequent items after this.
My frame refresh function (game.updateBoard()) does most of the necessary events for tetris to work, then calls itself using after. I call it once when initializing an instance of the game.
Instead of proceeding to mainloop(), the game.updateboard() function calls itself via after indefinitely.
I suspect that it is not behaving how I thought after worked which would be to continue to execute the script until the specified delay occurs. I think it is waiting for the callback to terminate to continue.
I tried to find a resource on this but could not.
If you have suggestions for fixing this question, the attached code, or for coding in general, I am very happy to hear them! This is a learning process and I'll gladly try pretty much anything you suggest.
Here is the relevant portion of the code:
class game():
def __init__(self): #Set up board and image board
self.pieces = ["L","J","S","Z","T","O","I"]
self.board = boardFrame()
self.root = Tk()
self.root.title("Tetris")
self.root.geometry("250x525")
self.frame = Frame(self.root)
#set up black and green squares for display
self.bSquare = "bsquare.gif"
self.gSquare = "square.gif"
self.rSquare = "rsquare.gif"
self.image0 = PhotoImage(file = self.bSquare)
self.image1 = PhotoImage(file = self.gSquare)
self.image2 = PhotoImage(file = self.rSquare)
#get an initial piece to work with
self.activeBlock = piece(self.pieces[random.randint(0,6)])
#Tells program to lower block every half second
self.blockTimer = 0
self.updateBoard()
self.root.bind('<KeyPress-Up>', self.turn)
self.root.bind('<KeyPress-Right>', self.moveR)
self.root.bind('<KeyPress-Left>', self.moveL)
self.root.bind('<KeyPress-Down>',self.moveD)
print("Entering mainloop")
self.root.mainloop()
def turn(self, event):
self.activeBlock.deOccupy(self.board)
self.activeBlock.turn()
self.activeBlock.occupy(self.board)
self.drawGrid(self.board.grid)
def moveR(self, event):
self.activeBlock.deOccupy(self.board)
self.activeBlock.updatePos([1,0], self.board)
self.activeBlock.occupy(self.board)
self.drawGrid(self.board.grid)
def moveL(self, event):
if self.activeBlock.checkLeft(self.board) == False:
self.activeBlock.deOccupy(self.board)
self.activeBlock.updatePos([-1,0], self.board)
self.activeBlock.occupy(self.board)
self.drawGrid(self.board.grid)
def moveD(self, event): #find
self.activeBlock.deOccupy(self.board)
self.activeBlock.updatePos([0,-1],self.board)
if self.activeBlock.checkBottom(self.board) == True:
self.activeBlock.occupy(self.board)
self.activeBlock = piece(self.pieces[random.randint(0,6)])
## self.activeBlock = piece(self.pieces[1])
print("bottomed")
self.activeBlock.occupy(self.board)
self.activeBlock.occupy(self.board)
self.drawGrid(self.board.grid)
def drawGrid(self, dGrid):
#Generate squares to match tetris board
for widget in self.frame.children.values():
widget.destroy()
self.activeBlock.occupy(self.board)
for x in range(9,-1,-1):
for y in range(20,-1,-1):
if self.board.grid[x][y] == 1:
self.frame.displayA = Label(self.frame, image=self.image1)
## self.frame.displayA.image = self.image1
self.frame.displayA.grid(row=21-y, column=x)
else:
self.frame.displayA = Label(self.frame, image = self.image0)
## self.frame.displayA.image = self.image0
self.frame.displayA.grid(row=21-y, column=x)
self.frame.displayA = Label(self.frame, image = self.image2)
self.frame.displayA.grid(row = 21 - self.activeBlock.center[1], column = self.activeBlock.center[0])
self.frame.grid()
def updateBoard(self):
self.blockTimer += 1
"print updateBoard Loop"
## 1)check for keyboard commands
#1.1 move block by keyboard commands
#2) see if block has bottomed out, if it has, have it enter itself into the grid and generate a new block.
if self.activeBlock.checkBottom(self.board) == True:
self.activeBlock.occupy(self.board)
self.activeBlock = piece(self.pieces[random.randint(0,6)])
print("bottomed")
self.activeBlock.occupy(self.board)
#2.2 - if block has not bottomed and 50 frames (~.5 seconds) have passed, move the active block down a square after clearing its old space.
elif self.blockTimer%12 == 0:
self.activeBlock.deOccupy(self.board)
self.activeBlock.updatePos([0,-1], self.board)
self.activeBlock.occupy(self.board)
## 4) check for filled rows
for y in range(1,21):
for x in range(10):
rowFull = True
if self.board.grid[x][y] == 0:
rowFull == False
#4.1 if any row is filled, delete it and then move all rows above the deleted row down by one
if rowFull == True:
for x2 in range(10):
self.board.grid[x2][y] = 0
for y2 in range(y+1,21):
if self.board.grid[x2][y2] == 1:
self.board.grid[x2][y2] = 0
self.board.grid[x2][y2-1] = 1
#4.11 if the row is full and the row above it was full, delete the row again as well as the row above it, and move all rows down by 2
for x in range(10):
rowFull = True
if self.board.grid[x][y] == 0:
rowFull == False
if rowFull == True:
for x2 in range(10):
try:
self.board.grid[x2][y] = 0
self.board.grid[x2][y+1] = 0
except:
pass
for y2 in range(y+2,21):
try:
if self.board.grid[x2][y2] == 1:
self.board.grid[x2][y2] = 0
self.board.grid[x2][y2-2] = 1
except:
pass
#5) if there is a block in the top row, end the game loop
for x in range(10):
if self.board.grid[x][20] == 1:
game = "over"
#6) update image
self.activeBlock.occupy(self.board)
self.drawGrid(self.board.grid)
self.frame.after(500, self.updateBoard())
Game = game()
You want to do self.frame.after(500, self.updateBoard).
The difference here is subtle, (self.updateBoard instead of self.updateBoard()). In your version, you're passing the result of your function to the after method instead of passing the function. This results in the infinite recursion that you described.

Categories