I'm working with Turtle, recreating the pong game as part of an intro course. I have 2 turtle objects, called paddles, stored in a dict with the relevant keys to move them, see below. I'm trying using a nested for loop for the binding with onkeypress(). As suggested elsewhere, I use the lambda function to pass the direction 'way' to the 'move' method, which I have in the Paddle class in another module.
Expected behavior in the MRE:
Behavior: only (1) works, (2) doesnt.
The main:
from turtle import Screen
from paddle import Paddle, PADDLE_KEY_MAP
screen = Screen()
paddles = PADDLE_KEY_MAP
# creates the paddles and adds it to the dictionary
for side in paddles:
paddles[side]["paddle"] = Paddle(side=side)
print(paddles) # for debugging
screen.update()
screen.listen()
for side in paddles:
for way in paddles[side]["way"]:
key = paddles[side]["way"][way]["key"]
print(side, way, key) # for debugging
screen.onkeypress(lambda way=way, key=key:
paddles[side]["paddle"].move(way), paddles[side]["way"][way]["key"])
screen.update()
screen.exitonclick()
and the paddles module. (Yes, the dict is ugly, I'm practicing only.)
from turtle import Turtle
PADDLE_KEY_MAP = {
"left":
{
"way":
{
"up":
{
"key": "w",
},
"down":
{
"key": "s",
},
}
},
"right":
{
"way":
{
"up":
{
"key": "Up",
},
"down":
{
"key": "Down",
},
}
}
}
class Paddle:
def __init__(self, side):
self.paddle = Turtle()
self.side = side
def move(self, way):
print("move method, side = ", self.side)
# showcasing the error: left is never recognized
if self.side == "left":
print("left is recognized")
If I explicitly use just one of the paddles in onkeypress(), then the binding works for either side, and the expected behavior occurs for that side. But it never works if both paddles are looped through. I also tried enforcing paddles within the for loop (if side == left then paddle = left paddle, else the right), but this failed too.
So I narrowed this down to the onkeypress
statement and/or the lambda function. Hope this makes sense.
Any suggestions? Thanks in advance
The issue is that those loop variables aren't bound to a closure, so they'll always have whatever the last values they had when the loop ended. Add a function closure to persist each iteration of the loop:
from turtle import Screen, Turtle
PADDLE_KEY_MAP = {
"left": {
"way": {
"up": {
"key": "w",
},
"down": {
"key": "s",
},
}
},
"right": {
"way": {
"up": {
"key": "Up",
},
"down": {
"key": "Down",
},
}
},
}
class Paddle:
def __init__(self, side):
self.paddle = Turtle()
self.side = side
def move(self, way):
print(self.side, way)
paddles = PADDLE_KEY_MAP
for side in paddles:
paddles[side]["paddle"] = Paddle(side)
screen = Screen()
screen.listen()
def bind(side, way):
screen.onkeypress(
lambda: paddles[side]["paddle"].move(way),
paddles[side]["way"][way]["key"]
)
for side in paddles:
for way in paddles[side]["way"]:
bind(side, way)
screen.exitonclick()
The syntax you had for onkeypress
was also incorrect and fixed above.
See How to bind several key presses together in turtle graphics? for a complete, working example of the bind()
function.
In general, you shouldn't need this large nested dict for such a simple keybinding operation--the approach feels overengineered. Try to avoid complicated nesting, boolean parameters (yes, you're using a string, but it's effectively a yes/no boolean), and premature generalization. There's only two paddles and two movements, so enumerate them explicitly:
from turtle import Screen, Turtle
class Paddle:
def __init__(self):
self.paddle = Turtle()
def move_up(self):
...
def move_down(self):
...
p1 = Paddle()
p2 = Paddle()
screen = Screen()
screen.listen()
screen.onkeypress(p1.move_up, "w")
screen.onkeypress(p2.move_down, "s")
screen.onkeypress(p1.move_up, "Up")
screen.onkeypress(p2.move_down, "Down")
screen.exitonclick()
For more complete versions of pong in turtle, showing the next steps in this design, see: