I'm using tkinter to make 2048 game gui, I created this function control_game
I want to have it such that anytime I click these keys (up, down, left, right) on my keyboard the control_game
function should be called from my main function. But anytime I run the code and press any of those keys this error is displayed: TypeError: main.<locals>.<lambda>() missing 1 required positional argument: 'gamepanel'
I have read about binding from this link
Main function
def main(gamepanel):
won = False
lost = False
get_random(gamepanel)
get_random(gamepanel)
gamepanel.update_board()
while not (won or lost):
gamepanel.root.bind_all("<Key>", lambda event, gamepanel: control_game(event, gamepanel))
gamepanel.root.focus_set()
gamepanel.root.mainloop()
Control game function
def control_game(event, gamepanel):
key_pressed = event.keysym
if key_pressed == "Up": # Up
transpose(gamepanel)
compress(gamepanel)
merge(gamepanel)
compress(gamepanel)
transpose(gamepanel)
elif key_pressed == "Down": #Down
transpose(gamepanel)
reverse(gamepanel)
compress(gamepanel)
merge(gamepanel)
compress(gamepanel)
reverse(gamepanel)
transpose(gamepanel)
elif key_pressed == "Left": #Left
compress(gamepanel)
merge(gamepanel)
compress(gamepanel)
elif key_pressed == "Right": # Right
reverse(gamepanel)
compress(gamepanel)
merge(gamepanel)
compress(gamepanel)
reverse(gamepanel)
This my board class:
class Board(Frame):
def __init__(self):
self.FOREGROUND_COLOR = {
2: "#776e65",
4: "#776e65",
8: "#f9f6f2",
16: "#f9f6f2",
32: "#f9f6f2",
64: "#f9f6f2",
128: "#f9f6f2",
256: "#f9f6f2",
512: "#f9f6f2",
1024: "#f9f6f2",
2048: "#f9f6f2",
4096: "#776e65",
8192: "#f9f6f2",
16384: "#776e65",
32768: "#776e65",
65536: "#f9f6f2",
}
self.BACKGROUND_COLOR = {
2: "#eee4da",
4: "#ede0c8",
8: "#f2b179",
16: "#f59563",
32: "#f67c5f",
64: "#f65e3b",
128: "#edcf72",
256: "#edcc61",
512: "#edc850",
1024: "#edc53f",
2048: "#edc22e",
4096: "#eee4da",
8192: "#edc22e",
16384: "#f2b179",
32768: "#f59563",
65536: "#f67c5f",
}
self.boardSize = 4
self.moved = False
self.merged = False
self.compressed = False
self.gameMatrix = []
self.gameTile = [self.boardSize * [0] for _ in range(self.boardSize)]
self.score = 0
self.root = Tk()
self.root.title("2048 by denstream-io")
self.windowStyle = ttk.Style()
self.windowStyle.configure(
"Grid.TFrame",
background="#92877D", # BBADAO
boderwidth=5,
relief="raised",
width="500p",
height="500p",
)
self.cellStyle = ttk.Style()
self.cellStyle.configure(
"Cell.TFrame",
# background="yellow",#EEE4DA
boderwidth=5,
)
self.labelStyle = ttk.Style()
self.labelStyle.configure(
"Label.TLabel",
anchor="center",
)
self.gameWindow = ttk.Frame(self.root, style="Grid.TFrame")
self.gameWindow.grid(sticky=(N, W, E, S))
for i in range(int(self.boardSize)):
labeled_row = []
for j in range(int(self.boardSize)):
self.gameCell = ttk.Frame(
self.gameWindow, relief="raised", style="Cell.TFrame"
)
self.gameCell.grid(column=i, row=j, sticky=(N, W, E, S))
self.eachLabel = ttk.Label(
self.gameWindow, text="", style="Label.TLabel"
)
self.eachLabel.grid(column=i, row=j, sticky=(N, W, E, S))
labeled_row.append(self.eachLabel)
self.gameMatrix.append(labeled_row)
for child in self.gameWindow.winfo_children():
child.grid_configure(padx=5, pady=5, ipadx="25p", ipady="25p")
def update_board(self):
for i in range(self.boardSize):
for j in range(self.boardSize):
if self.gameTile[i][j] == 0:
self.gameMatrix[i][j].configure(
text="", background="#9E948A"
)
else:
self.gameMatrix[i][j].configure(
text=str(self.gameTile[i][j]),
background=self.BACKGROUND_COLOR[self.gameTile[i][j]],
foreground=self.FOREGROUND_COLOR[self.gameTile[i][j]],
)
Entire Traceback:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\dennis-lc\AppData\Local\Programs\Python\Python310\lib\tkinter\__init__.py", line 1921, in __call__
return self.func(*args)
TypeError: main.<locals>.<lambda>() missing 1 required positional argument: 'gamepanel'
Anytime I exit the game frame, this error is displayed:
Traceback (most recent call last):
File "C:\Users\dennis-lc\Desktop\denstream-io\games\2048\2048\project.py", line 363, in <module>
main(gamepanel)
File "C:\Users\dennis-lc\Desktop\denstream-io\games\2048\2048\project.py", line 143, in main
gamepanel.root.bind_all("<Key>", lambda event, gamepanel: control_game(event, gamepanel))
File "C:\Users\dennis-lc\AppData\Local\Programs\Python\Python310\lib\tkinter\__init__.py", line 1435, in bind_all
return self._bind(('bind', 'all'), sequence, func, add, 0)
File "C:\Users\dennis-lc\AppData\Local\Programs\Python\Python310\lib\tkinter\__init__.py", line 1375, in _bind
self.tk.call(what (sequence, cmd))
_tkinter.TclError: can't invoke "bind" command: application has been destroyed
My code at the end of the file:
if __name__ == '__main__':
gamepanel = Board()
main(gamepanel)
CodePudding user response:
When an event callback is triggered, tkinter passes one positional argument containing information about the event.
This is your lambda definition:
lambda event, gamepanel: control_game(event, gamepanel))
It is roughly equivalent to this:
def something(event, gamepanel):
control_game(event, gamepanel)
Notice how it requires two parmeters: event
and gamepanel
. Since tkinter only passes a single parameter, you get the error missing 1 required positional argument: 'gamepanel'
which accurately describes the problem.
The solution is to convert the positional argument to a keyword argument with a default value:
lambda event, gamepanel=gamepanel: control_game(event, gamepanel))
# ^^^^^^^^^^
Tkinter will pass a single positional parameter, event
, and the other argument will be given the default value.
The error can't invoke "bind" command: application has been destroyed
is again accurately describing the problem. Consider this code:
while not (won or lost):
gamepanel.root.bind_all("<Key>", lambda event, gamepanel: control_game(event, gamepanel))
gamepanel.root.focus_set()
gamepanel.root.mainloop()
When the window is destroyed, gamepanel.root.mainloop()
will exit. When it exits, the while
loop will go to a second iteration. In that iteration you're trying to do a binding on the window that was just destroyed. Hence, can't invoke "bind" command: application has been destroyed
.
The solution is to remove the loop. There is almost never a reason to call mainloop
in a loop. If you feel you need to, then you need to make sure that each iteration recreates the window.