failed to resolve some method of tkinter

Question:

I am creating 2048 game on python with tkinter but I have a issue
I don’t know what is wrong in left, right, up and down methods created in PageOne class.
In another class (StartPage), bind function calls these methods but this don’t work
So I don’t know how toresolve the mistakes, in bind functions or in the left, right, up and down methods?
In particular I get this error when I press the right key:

Exception in Tkinter callback  
Traceback (most recent call last):  
File "C:UsersfabioAppDataLocalProgramsPythonPython310libtkinter__init__.py", line 1921, in __call__
return self.func(*args)  
File "C:UsersfabioPycharmProjects2048gametk2048gametk.py", line 198, in right
self.reverse()
AttributeError: 'Event' object has no attribute 'reverse'  

And similar error for the other key (up, left and down):

Traceback (most recent call last):  
File "C:UsersfabioAppDataLocalProgramsPythonPython310libtkinter__init__.py", line 1921, in __call__
return self.func(*args)  
File "C:UsersfabioPycharmProjects2048gametk2048gametk.py", line 208, in up
self.transpose()  
AttributeError: 'Event' object has no attribute 'transpose'

Exception in Tkinter callback  
Traceback (most recent call last):  
File "C:UsersfabioAppDataLocalProgramsPythonPython310libtkinter__init__.py", line 1921, in __call__
return self.func(*args)  
TypeError: PageOne.left() missing 1 required positional argument: 'event'

Exception in Tkinter callback
Traceback (most recent call last):
File "C:UsersfabioAppDataLocalProgramsPythonPython310libtkinter__init__.py", line 1921, in __call__
return self.func(*args)
File "C:UsersfabioPycharmProjects2048gametk2048gametk.py", line 218, in down self.transpose()
AttributeError: 'Event' object has no attribute 'transpose'

The left one is different because i put event instead of event = None as argument in the left module.
A small problem concern the score_frame that appears also in the StartPage and not only in the PageOne, so i don’t know how to combine the frames on the code

Below you can find the entire code
Can anyone help me?
Thanks in advance

import os
import random
import sys
import tkinter as tk
from math import floor

class MyButton(tk.Button):
    def __init__(self, *args, **kwargs):
        tk.Button.__init__(self, *args, **kwargs, bg='brown', fg='white',
                           font="Helvetica 12 bold", width=8, pady='1m')
        self.pack(pady="3m")



class Game(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        self.title("2048 game")
        self.dim = 4
        self.main_grid = None
        container = tk.Frame(self, width=500, height=600)
        container.grid(pady=(100, 0))
        container.grid_rowconfigure(0, weight=1)
        container.grid_columnconfigure(0, weight=1)
        self.frames = {}
        for F in (StartPage, PageOne):
            frame = F(container, self)
            self.frames[F] = frame
            frame.grid(row=0, column=0, sticky="nsew")
        self.show_frame(StartPage)

        self.bind("<Left>", PageOne.left)
        self.bind("<Right>", PageOne.right)
        self.bind("<Up>", PageOne.up)
        self.bind("<Down>", PageOne.down)
        self.mainloop()

    def show_frame(self, controller): #page_name al posto di controller
        frame = self.frames[controller]
        frame.tkraise()




class StartPage(tk.Frame):
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        self.controller = controller
        self.dim = controller.dim
        label = tk.Label(self, text="2048", font="Helvetica 48 bold", fg="orange")
        label.pack(side="top", fill="x")
        button1 = MyButton(self, text="3x3", command=self.dim_3)
        button1.pack()
        button1.bind("<Return>", self.dim_3)
        button2 = MyButton(self, text="4x4", command=self.dim_4)
        button2.focus_force()
        button2.pack()
        button2.bind("<Return>", self.dim_4)
        button3 = MyButton(self, text="5x5", command=self.dim_5)
        button3.pack()
        button3.bind("<Return>", self.dim_5)
        button4 = MyButton(self, text="8x8", command=self.dim_8)
        button4.pack()
        button4.bind("<Return>", self.dim_8)

    def dim_3(self):
        self.dim = 3
        self.controller.show_frame(PageOne)

    def dim_4(self):
        self.dim = 4
        self.controller.show_frame(PageOne)

    def dim_5(self):
        self.dim = 5
        self.controller.show_frame(PageOne)

    def dim_8(self):
        self.dim = 8
        self.controller.show_frame(PageOne)





class PageOne(tk.Frame):
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        self.controller = controller
        self.matrix = None
        self.score = None
        self.cells = []
        for i in range(self.controller.dim):
            row = []
            for j in range(self.controller.dim):
                cell_frame = tk.Frame(self, bg="light grey",
                                      width=500 / self.controller.dim, height=500 / self.controller.dim)
                cell_frame.grid(row=i+10, column=j, padx=10 / self.controller.dim, pady=10 / self.controller.dim)
                cell_number = tk.Label(self, bg="light grey")
                cell_number.grid(row=i+10, column=j)
                cell_data = {"frame": cell_frame, "number": cell_number}
                row.append(cell_data)
            self.cells.append(row)

        score_frame = tk.Frame(controller.main_grid)
        score_frame.place(relx=0.5, y=50, anchor="center")
        tk.Label(score_frame, text="Score", font="Helvetica 18 bold").grid(row=0)
        self.score_label = tk.Label(score_frame, text="0", font="Helvetica 24 bold")
        self.score_label.grid(row=1)
        self.start_game()


    def start_game(self):
        self.matrix = [[0] * self.controller.dim for _ in range(self.controller.dim)]
        row = random.randint(0, self.controller.dim - 1)
        col = random.randint(0, self.controller.dim - 1)
        self.matrix[row][col] = 2
        self.cells[row][col]["frame"].configure(bg="white")
        self.cells[row][col]["number"].configure(bg="white", fg="black", font="Helvetica 24 bold", text="2")
        while self.matrix[row][col] != 0:
            row = random.randint(0, self.controller.dim - 1)
            col = random.randint(0, self.controller.dim - 1)
        self.matrix[row][col] = 2
        self.cells[row][col]["frame"].configure(bg="white")
        self.cells[row][col]["number"].configure(bg="white", fg="black", font="Helvetica 24 bold", text="2")
        self.score = 0

    # move all numbers on the left side
    def stack(self):
        new_matrix = [[0] * self.controller.dim for _ in range(self.controller.dim)]
        for i in range(self.controller.dim):
            fill_pos = 0
            for j in range(self.controller.dim):
                if self.matrix[i][j] != 0:
                    new_matrix[i][fill_pos] = self.matrix[i][j]
                    fill_pos += 1
        self.matrix = new_matrix

    # sum numbers horizontally and combine equal numbers
    def combine(self):
        for i in range(self.controller.dim):
            for j in range(self.controller.dim - 1):
                if self.matrix[i][j] != 0 and self.matrix[i][j] == self.matrix[i][j + 1]:
                    self.matrix[i][j] *= 2
                    self.matrix[i][j + 1] = 0
                    self.score += self.matrix[i][j]

    # this function reverses the order of each row
    def reverse(self):
        new_matrix = []
        for i in range(self.controller.dim):
            new_matrix.append([])
            for j in range(self.controller.dim):
                new_matrix[i].append(self.matrix[i][self.controller.dim - 1 - j])
        self.matrix = new_matrix

    def transpose(self):
        new_matrix = [[0] * self.controller.dim for _ in range(self.controller.dim)]
        for i in range(self.controller.dim):
            for j in range(self.controller.dim):
                new_matrix[i][j] = self.matrix[j][i]
        self.matrix = new_matrix

    def add_number(self):
        if any(0 in row for row in self.matrix):
            row = random.randint(0, self.controller.dim - 1)
            col = random.randint(0, self.controller.dim - 1)
            while self.matrix[row][col] != 0:
                row = random.randint(0, self.controller.dim - 1)
                col = random.randint(0, self.controller.dim - 1)
            self.matrix[row][col] = random.choice([2, 2, 2, 2, 4])

    def update_GUI(self):
        for i in range(self.controller.dim):
            for j in range(self.controller.dim):
                cell_value = self.matrix[i][j]
                if cell_value == 0:
                    self.cells[i][j]["frame"].configure(bg="light grey")
                    self.cells[i][j]["number"].configure(bg="light grey", text="")
                else:
                    self.cells[i][j]["frame"].configure(bg=NUMBER_COLOR[cell_value])
                    self.cells[i][j]["number"].configure(bg=NUMBER_COLOR[cell_value], fg="black",
                                                         font="Helvetica 24 bold", text=str(cell_value))
        self.score_label.configure(text=self.score)
        self.update_idletasks()

        self.game_over()

    def left(self, event):
        self.stack()
        self.combine()
        self.stack()
        self.add_number()
        self.update_GUI()
        self.game_over()

    def right(self, event=None):
        self.reverse()
        self.stack()
        self.combine()
        self.stack()
        self.reverse()
        self.add_number()
        self.update_GUI()
        self.game_over()

    def up(self, event=None):
        self.transpose()
        self.stack()
        self.combine()
        self.stack()
        self.transpose()
        self.add_number()
        self.update_GUI()
        self.game_over()

    def down(self, event=None):
        self.transpose()
        self.reverse()
        self.stack()
        self.combine()
        self.stack()
        self.reverse()
        self.transpose()
        self.add_number()
        self.update_GUI()

    def horiz_moves(self):
        for i in range(self.controller.dim):
            for j in range(self.controller.dim - 1):
                if self.matrix[i][j] == self.matrix[i][j + 1]:
                    return True
        return False

    def vert_moves(self):
        for i in range(self.controller.dim - 1):
            for j in range(self.controller.dim):
                if self.matrix[i][j] == self.matrix[i + 1][j]:
                    return True
        return False

    def number(self):
        if self.controller.dim == 3:
            return 8
        elif self.controller.dim == 4:
            return 2048
        elif self.controller.dim == 5:
            return 8192
        else:
            return 16384

    def restart(self):
        self.bind("<Return>", os.execl(sys.executable, sys.executable, *sys.argv))

    def game_over(self):
        tk.Frame(self).place(relx=0.5, rely=0.5, anchor="center")
        if not any(0 in row for row in self.matrix) and not self.horiz_moves() and not self.vert_moves():
            top = tk.Toplevel()
            top.geometry("300x100")
            top.title("toplevel")
            l2 = tk.Label(top, text="Game Overn What do you want do?",
                          fg="white", font="Helvetica 12 bold")
            l2.pack()
            tk.Button(top, text="Restart", bg="green",
                      fg="white", font="Helvetica 10 bold",
                      command=self.restart).pack(side='left')
            tk.Button(top, text="Quit", bg="red",
                      fg='white', font="Helvetica 10 bold", command=self.bind("<Return>", quit)).pack(side="right")

        elif any(self.number() in row for row in self.matrix):
            top = tk.Toplevel()
            top.geometry("180x100")
            top.title("toplevel")
            l2 = tk.Label(top, text="You Win!n What do you want do?", font="Helvetica 12 bold")
            l2.pack()
            tk.Button(top, text="Restart", bg="green",
                      fg="white", font="Helvetica 10 bold",
                      command=self.restart).pack(side='left')
            tk.Button(top, text="Quit", bg="red",
                      fg='white', font="Helvetica 10 bold", command=self.bind("<Return>", quit)).pack(side="right")

    def savegame(self):
        f = open("savedata", "w")

        line1 = " ".join(
            [str(self.matrix[floor(x / self.controller.dim)][x % self.controller.dim])
             for x in range(0, self.controller.dim ** 2)])
        f.write(line1 + "n")
        f.write(str(self.controller.dim) + "n")
        f.write(str(self.score))
        f.close()

    def loadgame(self):
#        self.score
#        self.dim
#        self.matrix

        f = open("savedata", "r")

        mat = (f.readline()).split(' ', self.controller.dim ** 2)
        self.controller.dim = int(f.readline())
        self.score = int(f.readline())

        for i in range(0, self.controller.dim ** 2):
            self.matrix[floor(i / self.controller.dim)][i % self.controller.dim] = int(mat[i])

        f.close()



NUMBER_COLOR = {
    2: "#fcefe6",
    4: "#f2e8cb",
    8: "#f5b682",
    16: "#f29446",
    32: "#ff775c",
    64: "#e64c2e",
    128: "#ede291",
    256: "#fce130",
    512: "#ffdb4a",
    1024: "#f0b922",
    2048: "#fad74d",
    4096: "brown",
    8192: "silver",
    16384: "gold"
}


def main():
    Game()


if __name__ == "__main__":
    main()

Asked By: Fabio Citti

||

Answers:

Replace the code with the following:

    self.bind("<Left>", self.frames[PageOne].left)
    self.bind("<Right>", self.frames[PageOne].right)
    self.bind("<Up>", self.frames[PageOne].up)
    self.bind("<Down>", self.frames[PageOne].down)

The reason for your odd behavior is that you try to reach instances variables and methods but on the level of a class. It’s not easy to explain in a few words, but you should research the difference between class an regular methods, the purpose of self and of course the documentation for classes and instances

Update

In order to make your dimensions work you need to create your matrix after you defined the dimensions, not when you initialize your Frame. The shortest way to achive this, is to create a function that is called when your dimension is defined.

In your PageOne you would do something like:

    ...
    self.cells = []

def create_matrix(self):
    for i in range(self.controller.dim):
    ...
    score_frame = tk.Frame(self.controller.main_grid) #add self.
    ...

call this function when the user has chosen by (e.g):

def dim_3(self):
    self.controller.dim = 3 #use self.controller dim
    self.controller.frames[PageOne].create_matrix() #call create_matrix 
    self.controller.show_frame(PageOne)

and while you use the variable in your controller the following is not effective:

class StartPage(tk.Frame):
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        self.controller = controller
        #self.dim = controller.dim
        #is not a link to your var self.dim stores whats in controller.dim 

Update2

in order to make your quit and restart work and with little improvements I have changed your code below. The code is left as an exercise since I still try to figure out the rules of the game. Happy codeing!

def restart(self):
    for widget in self.winfo_children():
        widget.destroy()
    self.cells = []
    self.controller.show_frame(StartPage)

def game_over(self):
    tk.Frame(self).place(relx=0.5, rely=0.5, anchor="center")
    if not any(0 in row for row in self.matrix) and not self.horiz_moves() and not self.vert_moves():
        geo_string = "300x100"
        title = "Game Over!"
        text = "Game Overn What do you want do?"
        self.message(geo_string,title,text)
    elif any(self.number() in row for row in self.matrix):
        geo_string = "180x100"
        title = "Congratulation!"
        text = "You Win!n What do you want do?"
        self.message(geo_string,title,text)

def message(self, geo_string,title,text):
    top = tk.Toplevel()
    top.geometry(geo_string)
    top.title(title)
    l2 = tk.Label(top, text=text, font="Helvetica 12 bold")
    l2.pack()
    rbutton = tk.Button(top, text="Restart", bg="green",
                        fg="white", font="Helvetica 10 bold",
                        command=self.restart)
    rbutton.bind('<ButtonRelease-1>',lambda e: self.controller.after(20,top.destroy))
    rbutton.pack(side='left')
    
    qbutton = tk.Button(top, text="Quit", bg="red",
                        fg='white', font="Helvetica 10 bold",
                        command=self.controller.destroy)
    qbutton.pack(side="right")
Answered By: Thingamabobs
Categories: questions Tags: , , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.