I am attempting to animate the series of button presses in the simon game I am creating with tkinter. The animate method is called for each button that needs to be pressed in the series. It currently presses all of the buttons at the same time rather than in a series as it is meant to.

from tkinter import *
from random import choice
root = Tk()
Grid.rowconfigure(root, 0, weight=1)
Grid.columnconfigure(root, 0, weight=1)
frame = Frame(root)
frame.grid(row=0, column=0, sticky=N+E+S+W)

def animate(btn):
    btn.config(relief=SUNKEN, state=ACTIVE)
    btn.after(200, lambda: btn.config(relief=RAISED, state=NORMAL))

class Game(object):
    def __init__(self):
        self.colors = ["green", "red", "yellow", "blue"]
        self.i = 0
        self.round = 1
        self.moves = []
        self.cpu_moves = []

        self.btns = []
        for row in range(2):
            Grid.rowconfigure(frame, row, weight=1)
            for col in range(2):
                Grid.columnconfigure(frame, col, weight=1)
                btn = Button(frame, width=150, height=150, bg=self.colors[0],
                             command=lambda b=self.i: self.user_select(b))
                btn.grid(row=row, column=col)
                self.i += 1

    def user_select(self, btn):
        if self.moves == self.cpu_moves:
            self.round += 1
        elif len(self.moves) == len(self.cpu_moves) and self.moves != self.cpu_moves:

    def clear_user_moves(self):
        self.moves = []

    def clear_cpu_moves(self):
        self.cpu_moves = []

    def cpu_move(self):
        nums = [0, 1, 2, 3]
        for i in self.cpu_moves:
            root.after(200, animate(self.btns[i]))

class App(object):
    def __init__(self, master):
        self.master = master
        game = Game()


app = App(root)
root.resizable(width=False, height=False)

1 Answer 1


Let's look at this code:

for i in self.cpu_moves:
    root.after(200, animate(self.btns[i]))

First, you're not using after correctly. You're doing the equivalent of this:

for i in self.cpu_moves:
    result = animate(self.btns[i])
    root.after(200, result)

You might as well not call root.after at all, since result will be None. If you need to pass parameters to the function you can do so by adding arguments to after itself:

root.after(200, animate, self.btns[i])

Unfortunately, you can only do this with positional parameters, not named parameters. You still need to use lambda the way you are doing it in animate.

Second, this loop is going to end up calling every function at the same time - the first one at "now+200ms", the next one at "now+200ms", the third at "now+200ms" and so on. What you want is the first one to be at "now+200ms", the next at "now+400ms" and so on. A simple solution is to add 200 for each iteration of the loop:

for i in self.cpu_moves:
    root.after(delay, animate(self.btns[i]))
    delay += 200

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.