питон черепаха странный курсор прыгать

Я пытаюсь использовать turtle draw с мышью, я получил ниже демо-код работает, но курсор прыгать иногда во время движения мыши:

#!/usr/bin/env python
import turtle
import sys

width = 600
height = 300
def gothere(event):
    turtle.penup()
    x = event.x
    y = event.y
    print "gothere (%d,%d)"%(x,y)
    turtle.goto(x,y)
    turtle.pendown()

def movearound(event):
    x = event.x
    y = event.y
    print "movearound (%d,%d)"%(x,y)
    turtle.goto(x,y)

def release(event):
    print "release"
    turtle.penup()

def circle(x,y,r):
    turtle.pendown() 
    turtle.goto(x,y)
    turtle.circle(r)
    turtle.penup()
    return

def reset(event):
    print "reset"
    turtle.clear()

#------------------------------------------------#
sys.setrecursionlimit(90000)
turtle.screensize(canvwidth=width, canvheight=height, bg=None)
turtle.reset()
turtle.speed(0)
turtle.setup(width, height)

canvas = turtle.getcanvas()

canvas.bind("<Button-1>", gothere)
canvas.bind("<B1-Motion>", movearound)
canvas.bind("<ButtonRelease-1>", release)
canvas.bind("<Escape>",reset)

screen = turtle.Screen()
screen.setworldcoordinates(0,height,width,0)
screen.listen()

turtle.mainloop()
#------------------------------------------------#

см. ниже gif для фактического поведения:

enter image description here

Не уверен, что какой-либо вызов API ошибочен!

3 ответов


Я вижу несколько проблем с вашим кодом:

  • вы смешиваете объектно-ориентированный интерфейс с turtle с функциональный интерфейс к этому модулю. Я рекомендую один или другие, но не оба сразу. Смотрите мой import изменить на force OOP-only.

  • вы используете низкоуровневую мышь tkinter и ключевые события вместо собственные события черепахи. Я рекомендую вам попробовать работать на уровне черепахи (хотя это вводит Глюк по сравнению с вашим реализации см. под.)

  • вы вводите непреднамеренную рекурсию, не выключая события внутри обработчиков событий. Отключение событий внутри этих обработчиков это займет значительное время, чтобы очистить вашу графику.

вот моя переделка вашего кода по вышеуказанным строкам. Один глюк заключается в том, что в отличие от вашего оригинала, "переместить черепаху здесь" и "начать перетаскивание" займет два клика, один щелчок экрана, чтобы переместить черепаху в текущее местоположение и один черепаха нажмите, чтобы начать перетаскивание. Это связано с различиями в интерфейсе, который turtle предоставляет событиям tkinter. (Python 3 немного лучше в этом отношении, но не для этой ситуации.)

чтобы облегчить это, я использовал больший курсор черепахи. Я также добавил заголовок логика:

from turtle import Turtle, Screen, mainloop

WIDTH = 600
HEIGHT = 300

def gothere(x, y):
    screen.onscreenclick(gothere)  # disable events inside handler

    turtle.penup()
    print("gothere (%d,%d)" % (x, y))
    turtle.goto(x, y)
    turtle.pendown()

    screen.onscreenclick(gothere)

def movearound(x, y):
    turtle.ondrag(None)  # disable events inside handler

    turtle.setheading(turtle.towards(x, y))
    print("movearound (%d,%d)" % (x, y))
    turtle.goto(x, y)

    turtle.ondrag(movearound)

def release(x, y):
    print("release (%d,%d)" % (x, y))
    turtle.penup()

def reset():
    print("reset")
    turtle.clear()

screen = Screen()
screen.setup(WIDTH, HEIGHT)
# screen.setworldcoordinates(0, HEIGHT, WIDTH, 0)  # should work fine either way

turtle = Turtle('turtle')
turtle.speed('fastest')

turtle.ondrag(movearound)
turtle.onrelease(release)

screen.onscreenclick(gothere)
screen.onkey(reset, "Escape")

screen.listen()

mainloop()  # normally screen.mainloop() but not in Python 2

, но и ответ где я показываю, как сделать tkinter это onmove событие доступно для turtle.

... "переехать сюда" тогда ограничение "начать перетаскивание" очень не удобный для потребителя? Как мы можем это улучшить?

объединение моего кода выше с моим альтернативным ответом, с которым я связан, мы получаем это решение, похожее на то, где вы начали, но без сбоев и в более черепашьем стиле:

from turtle import Turtle, Screen, mainloop
from functools import partial

WIDTH = 600
HEIGHT = 300

VERBOSE = False

def onscreenmove(self, fun, btn=1, add=None):  # method missing from turtle.py

    if fun is None:
        self.cv.unbind('<Button%s-Motion>' % btn)
    else:
        def eventfun(event):
            fun(self.cv.canvasx(event.x) / self.xscale, -self.cv.canvasy(event.y) / self.yscale)

        self.cv.bind('<Button%s-Motion>' % btn, eventfun, add)

def onscreenrelease(self, fun, btn=1, add=None):  # method missing from turtle.py

    if fun is None:
        self.cv.unbind("<Button%s-ButtonRelease>" % btn)
    else:
        def eventfun(event):
            fun(self.cv.canvasx(event.x) / self.xscale, -self.cv.canvasy(event.y) / self.yscale)

        self.cv.bind("<Button%s-ButtonRelease>" % btn, eventfun, add)

def gothere(x, y):

    if VERBOSE:
        print("gothere (%d,%d)" % (x, y))

    turtle.penup()
    turtle.goto(x, y)
    turtle.pendown()

def movearound(x, y):

    screen.onscreenmove(None)  # disable events inside handler

    if VERBOSE:
        print("movearound (%d,%d)" % (x, y))


    turtle.setheading(turtle.towards(x, y))
    turtle.goto(x, y)

    screen.onscreenmove(movearound)  # reenable events

def release(x, y):

    if VERBOSE:
        print("release (%d,%d)" % (x, y))

    turtle.penup()

def reset():

    if VERBOSE:
        print("reset")

    turtle.clear()

screen = Screen()
screen.setup(WIDTH, HEIGHT)
screen.onscreenrelease = partial(onscreenrelease, screen)  # install missing methods
screen.onscreenmove = partial(onscreenmove, screen)

turtle = Turtle('turtle')
turtle.speed('fastest')

screen.onscreenclick(gothere)
screen.onscreenrelease(release)
screen.onscreenmove(movearound)

screen.onkey(reset, "Escape")
screen.listen()

mainloop()  # normally screen.mainloop() but not in Python 2

Я согласен с cdlane, что было бы лучше избегать привязки событий уровня Tkinter, где это возможно, но я подумал, что было бы также интересно подойти к проблеме, не сильно изменяя общий дизайн. Мое решение требует только дополнительного импорта:

from collections import deque

новая версия movearound:

pending = deque()
def movearound(event):
    x = event.x
    y = event.y
    pending.append((x,y))
    if len(pending) == 1:
        while pending:
            x,y = pending[0]
            turtle.goto(x,y)
            pending.popleft()

как я указал в своем комментарии goto можно назвать movearound если резервная копия очереди событий окна, и это может вызвать переполнение стека или гонки условия, которые заставляют черепаху двигаться необычным образом. Этот подход направлен на предотвращение произвольно глубокой рекурсии, позволяя только самый верхний экземпляр movearound вызов goto. if len(pending) == 1: должно выполняться только для нерекурсивных вызовов; все рекурсивные вызовы будут видеть очередь больше, чем это. The while pending: loop работает через все встроенные события, обрабатывая их в порядке, в котором они прибыли.

результат: черепаха, которая послушно следует по пути курсора, хотя и в своей собственной turtley Пейс:

enter image description here


добавлять turtle.tracer(2,0) Кажется, что проблема исчезает. Это может быть временное решение, так как я не знаю, просто ли оно скрывает проблему за другой.

...
screen = turtle.Screen()
screen.setworldcoordinates(0,height,width,0)
screen.listen()
turtle.tracer(2, 0)  # This line

turtle.mainloop()
...

в документации мы можем прочитать:

включает/выключает анимацию черепахи и устанавливает задержку для обновления чертежей.

как указывает @Keven, проблема, похоже, неявная update в обратного вызова. Это уменьшает количество update звонок 2. Я думаю, что он также может удалить рекурсивный вызов.

примечание: Я не знаю, почему, но убрать screen.setworldcoordinates(0,height,width,0) удаляет поведение Глюка, но рекурсивный вызов update по-прежнему присутствует.