簡體   English   中英

需要幫助在 Mac 上調試 Python turtle 模塊

[英]Need help debugging the Python turtle module on Mac

我正在嘗試調試我在學生的舊 Mac 計算機上構建的圖形模塊的一些奇怪行為。 我試圖只復制下面的相關內容,但我想我的問題可能會回答為什么會這樣的問題。

所以,如果你運行下面的代碼,你會看到一個橢圓在一個矩形中跳來跳去。 粗略地說,它的工作方式是橢圓由大約 200 個點組成,這些點基本上充當近似橢圓的多邊形。 有趣的是,在我學生的 2016 年 macbook air 上存在嚴重的“繪圖”錯誤。 當 Shape 在屏幕上移動時,它會擦除當前繪制的 Shape 的像素,然后在新位置繪制 Shape。 我沒有親自實施任何線程,因此理論上應該在進行任何新繪圖之前完成所有取消繪圖。 然而,當這個橢圓在他們的計算機屏幕上來回跳動時,許多線段並沒有被正確地展開,但橢圓在它的新位置被正確地重繪了。 更有趣的是,這只發生在 class 的 2 名學生的筆記本電腦上,他們都恰好擁有舊的 macbook airs。

所以我不知道該怎么做。 我的第一個想法是,由於 turtle 沒有實現任何線程,而是建立在 tkinter 之上,因此 tkinter 可能使用了某種程度的線程,但我不完全確定到什么程度,我希望有人可能能夠比我 go 更快地通過整個 tkinter 的源代碼對此發表評論,以嘗試了解可能發生的事情,以及我可以做些什么來解決它。

或者,也許我完全錯了,這與那無關。 我會很感激任何想法。

import turtle
from typing import Union
import abc
import time
import math
turtle.tracer(False)
turtle.colormode(255)

class Window:

    def __init__(self, title: str, width: int, height: int):
        """ The constructor for the Window class. 
        
            Creates the turtle._Screen object _screen according to the given 
            title, width, and height parameters given. 
            
            Args:
                title (str):    A string to title the window.
                width (int):    An int to specify the window width.
                height (int):   An int to specify the window height.
        """

        self._screen = turtle.Screen()
        self._screen.title(title)
        self._screen.setup(width+20, height+20)

    def setBackground(self, color: Union[str, tuple[float, float, float]]):
        """ Sets the background color of the screen.
        
            Args: 
                color (str or (float, float, float)): The desired color of 
                                the background.
        """
        self._screen.bgcolor(color)


class Shape(abc.ABC):
    _turt: turtle.Turtle
    _drawn: bool
    _points: list[tuple[float, float]]
    _filled: bool
    _center: float
    _screen: turtle._Screen

    def __init__(self, lineWidth: int,
                 lineColor: Union[str, tuple[int, int, int]] = None,
                 fillColor: Union[str, tuple[int, int, int]] = None):
        """ This initializes the instance variables common to all shapes. 
            This includes: 

                self._turt:     The turtle.Turtle object which draws the shape.
                                It has a pensize of lineWidth, a pencolor of 
                                lineColor, and a fill color of fillColor.
                self._drawn:    A boolean which is True if and only if the shape
                                is currently drawn to the screen. It is False by
                                default.
                self._filled:   A boolean which is True if and only if the shape
                                will be filled in when drawn. It is True when
                                fillColor is given.
                self._screen:   A Screen which the shape will be drawn to.
                self._points:   A list of tuples containing the points which
                                comprise the shape.
            
            Args:
                lineWidth (int): The line width of the outer edge
                lineColor (str or (int, int, int)): The color of the outer edge.
                fillColor (str or (int, int, int)): The color to fill the shape 
                                in.
        """
        self._turt = turtle.Turtle()
        self._turt.penup()
        self._turt.hideturtle()

        self._turt.pensize(lineWidth)
        self._filled = False
        if lineColor:
            self._turt.pencolor(lineColor)
        if fillColor:
            self._turt.fillcolor(fillColor)
            self._filled = True

        self._drawn = False
        self._screen = turtle.Screen()

        self._points = []

    def setFill(self, color: Union[str, tuple[int, int, int]]):
        """ Configures the given shape to be filled with the given parameter 
            color.
        
            Args: 
                color (str or (int, int int)): The color to fill in the shape
        """
        if color: 
            self._filled = True
            self._turt.fillcolor(color)
            if self._drawn:
                self.undraw()
                self.draw()

    @abc.abstractmethod
    def _determinePoints(self):
        """ Determines the underlying points of the shape.
        
            To be implemented by inherited classes.
        """

    def draw(self):
        """ Draws the shape to the screen"""
        if not self._drawn:
            self._turt.goto(self._points[0])

            self._turt.pendown()
            if self._filled:
                self._turt.begin_fill()
            for point in self._points:
                self._turt.goto(point)
            if self._filled:
                self._turt.end_fill()
            self._turt.penup()

            self._screen.update()
            self._drawn = True

    def undraw(self):
        """ Undraws the shape from the screen"""
        self._turt.clear()
        self._drawn = False

    def move(self, dx: float = 0, dy: float = 0):
        """ Moves the shape in the x direction by dx and in the y direction by
            dy
            
            Args:
                dx (float):     The amount by which to move the shape to the 
                                right
                dy (float):     The amount by which to move the shape up """
        for i in range(len(self._points)):
            self._points[i] = (self._points[i][0] + dx,
                               self._points[i][1] + dy)
        self._center = (self._center[0] + dx, self._center[1] + dy)

        if self._drawn:
            self.undraw()
            self.draw()

    def rotate(self, dTheta: float = 0):
        """ Rotates the shape counter-clockwise by the angle dTheta measured 
            in degrees.
            
            Args: 
                dTheta (float): The number of degrees by which to rotate.
        """
        for i in range(len(self._points)):
            xOffset = self._points[i][0] - self._center[0]
            yOffset = self._points[i][1] - self._center[1]
            self._points[i] = (
                self._center[0] + xOffset * math.cos(dTheta * 2*math.pi / 360)
                - yOffset * math.sin(dTheta * 2*math.pi / 360),
                self._center[1] + xOffset * math.sin(dTheta * 2*math.pi / 360)
                + yOffset * math.cos(dTheta * 2*math.pi / 360))

        self._turt.left(dTheta)

        if self._drawn:
            self.undraw()
            self.draw()

class Rectangle(Shape):

    def __init__(self,
                 lowerLeftCorner: tuple[int, int], width: int, height: int,
                 lineWidth: int = 1,
                 lineColor: Union[str, tuple[int, int, int]] = None,
                 fillColor: Union[str, tuple[int, int, int]] = None):
        """ The constructor for the Rectangle Class. 
        
            In addition to the tasks performed by the constructor for the Shape
            class, this sets the lowerLeftCorner parameter of the shape, as well
            as calling the _determinePoints method to construct the 4 defining
            points of the rectangle.

            Args:
                lowerLeftCorner ((int, int)):               
                                A point denoting the bottom left corner of the 
                                rectangle
                width (int):    The width of the rectangle
                height (int):   The height of the rectangle
                lineWidth (int): The penwidth of the outer edge
                lineColor (str or (float, float, float)): The pencolor of the 
                                outer edge
                fillColor (str or (float, float, float)): The color used to fill
                                the shape """
        Shape.__init__(self, lineWidth, lineColor, fillColor)

        self.lowerLeftCorner = lowerLeftCorner
        self.width = width
        self.height = height

        self._determinePoints()

    def _determinePoints(self):
        """ Assembles the 4 defining points of the Rectangle and saves them in 
            _points.
        """
        self._points = [
            self.lowerLeftCorner,
            (self.lowerLeftCorner[0] + self.width,
                self.lowerLeftCorner[1]),
            (self.lowerLeftCorner[0] + self.width,
                self.lowerLeftCorner[1] + self.height),
            (self.lowerLeftCorner[0],
                self.lowerLeftCorner[1] + self.height),
            self.lowerLeftCorner]
        self._center = (self.lowerLeftCorner[0] + self.width/2,
                        self.lowerLeftCorner[1] + self.height/2)


class Oval(Shape):

    def __init__(self, center: tuple[float, float], width: int,
                 height: int, steps: int = 500, lineWidth: int = 1,
                 lineColor: Union[str, tuple[int, int, int]] = None,
                 fillColor: Union[str, tuple[int, int, int]] = None):
        """ The constructor for the Oval Class.
        
            In addition to the tasks performed by the constructor for the Shape
            class, this sets the center of the shape, as well as calling the 
            _determinePoints method to construct the points of the ellipse. The
            steps parameter specifies the number of points to use for the
            construction of the outline.

            Args:
                center ((float, float)): A point denoting the center of the oval
                width (float):  The width of the oval
                height (float): The height of the oval
                steps (int):    The number of points to create for the oval
                lineWidth (int): The line width of the outer edge
                lineColor (str or (int, int, int)): The pencolor of the outer 
                                edge
                fillColor (str or (int, int, int)): The color used to fill the 
                                given shape 
        """
        Shape.__init__(self, lineWidth, lineColor, fillColor)

        self._center = center
        self._width = width
        self._height = height

        self._steps = steps
        self._determinePoints()

    def _determinePoints(self):
        """ Populates _points with as many points as specified by steps via the
            parametric equations for an ellipse."""
        for i in range(self._steps):
            self._points.append(
                (self._center[0] + 
                    self._width * math.cos(i*2*math.pi/self._steps),
                 (self._center[1] + 
                    self._height * math.sin(i*2*math.pi/self._steps))))
        self._points.append(self._points[0])

if __name__ == "__main__":
    window = Window("Tester", 600, 600)
    Rectangle((-200, -250), 400, 500, 5, fillColor="Green").draw()

    oval = Oval((-50, -50), 100, 50, 200, 3, "Red", "Blue")
    oval.draw()

    direction = [3, 3]
    rotation = 3

    def updateDirection():
        global direction, rotation
        if max([*zip(*oval._points)][0]) >= 200:
            direction[0] = -3
            if direction[1] > 0:
                rotation = -3
            else:
                rotation = 3
        elif min([*zip(*oval._points)][0]) <= -200:
            direction[0] = 3
            if direction[1] > 0:
                rotation = 3
            else:
                rotation = -3
        if max([*zip(*oval._points)][1]) >= 250:
            direction[1] = -3
            if direction[0] > 0:
                rotation = 3
            else:
                rotation = -3
        elif min([*zip(*oval._points)][1]) <= -250:
            direction[1] = 3
            if direction[0] > 0:
                rotation = -3
            else:
                rotation = 3

    time.sleep(1)
    while(True):
        oval.move(direction[0], direction[1])
        updateDirection()
        oval.rotate(rotation)
        time.sleep(1/65)

我無法在我的系統上重現您的問題,但通過代碼重新考慮了 turtle-wise。 我注意到的一件事是draw()undraw()在它們的邏輯上不對稱,所以我修復了這個問題並引入了redraw()來優化undraw();draw()組合。

我還在代碼中注意到,一些海龜操作會獨立於代碼觸發_screen.update() 我用計時器事件替換了while True:循環:

from turtle import Screen, _Screen, Turtle
from typing import Union, List, Tuple
from abc import ABC, abstractmethod
from math import sin, cos, pi

Color = Union[str, Tuple[float, float, float]]

class Window:

    def __init__(self, title: str, width: int, height: int):
        """ The constructor for the Window class.

        Creates the turtle._Screen object _screen according to the given
        title, width, and height parameters given.

        Args:
            title (str): A string to title the window.
            width (int): An int to specify the window width.
            height (int): An int to specify the window height.
        """

        self._screen = Screen()
        self._screen.title(title)
        self._screen.setup(width+20, height+20)
        self._screen.tracer(False)

class Shape(ABC):
    _turtle: Turtle
    _drawn: bool
    _points: List[Tuple[float, float]]
    _filled: bool
    _center: float
    _screen: _Screen

    def __init__(self, lineWidth: int, \
        lineColor: Color = None, \
        fillColor: Color = None):

        """ This initializes the instance variables common to all shapes.

        This includes:

            self._turtle:   The Turtle object which draws the shape.
                    It has a pensize of lineWidth, a pencolor of
                    lineColor, and a fill color of fillColor.
            self._drawn:    A boolean which is True if and only if the shape
                    is currently drawn to the screen. False by default.
            self._filled:   A boolean which is True if and only if the shape
                    will be filled in when drawn. It is True when
                    fillColor is given.
            self._screen:   A Screen which the shape will be drawn to.
            self._points:   A list of tuples containing the points which
                    comprise the shape.

        Args:
            lineWidth (int): The line width of the outer edge
            lineColor (Color): The color of the outer edge.
            fillColor (Color): The color to fill the shape in.
        """

        self._turtle = Turtle()
        self._turtle.hideturtle()
        self._turtle.penup()
        self._turtle.pensize(lineWidth)

        self._filled = False

        if lineColor:
            self._turtle.pencolor(lineColor)

        if fillColor:
            self._turtle.fillcolor(fillColor)
            self._filled = True

        self._drawn = False
        self._screen = Screen()

        self._points = []

    def setFill(self, color: Color):
        """ Configures the given shape to be filled with the given parameter color.

        Args:
            color (Color): The color to fill in the shape
        """

        if color:
            self._filled = True
            self._turtle.fillcolor(color)

            self.redraw()

    @abstractmethod
    def _determinePoints(self):
        """
            Determines the underlying points of the shape.

            To be implemented by inherited classes.
        """

    def draw(self):
        """ Draws the shape to the screen """

        if not self._drawn:
            self._turtle.goto(self._points[0])

            if self._filled:
                self._turtle.begin_fill()

            self._turtle.pendown()  # causes _screen.update()

            for point in self._points:
                self._turtle.goto(point)

            self._turtle.penup()

            if self._filled:
                self._turtle.end_fill()  # causes _screen.update()

            self._screen.update()
            self._drawn = True

    def undraw(self):
        """ Undraws the shape from the screen """

        if self._drawn:
            self._turtle.clear()
            self._screen.update()
            self._drawn = False

    def redraw(self):
        """ (Re)draws the shape to the screen """

        if self._drawn:
            self._turtle.clear()
            self._turtle.goto(self._points[0])

            if self._filled:
                self._turtle.begin_fill()

            self._turtle.pendown()  # causes _screen.update()

            for point in self._points:
                self._turtle.goto(point)

            self._turtle.penup()

            if self._filled:
                self._turtle.end_fill()  # causes _screen.update()

            self._screen.update()

    def move(self, dx: float = 0, dy: float = 0):
        """ Moves the shape in the x direction by dx and in the y direction by dy

        Args:
            dx (float): The amount by which to move the shape to the right
            dy (float): The amount by which to move the shape up
        """

        for i in range(len(self._points)):
            self._points[i] = (self._points[i][0] + dx, self._points[i][1] + dy)

        self._center = (self._center[0] + dx, self._center[1] + dy)

        self.redraw()

    def rotate(self, dTheta: float = 0):
        """ Rotates the shape counter-clockwise by the angle dTheta measured in degrees.

        Args:
            dTheta (float): The number of degrees by which to rotate.
        """

        for i in range(len(self._points)):
            xOffset = self._points[i][0] - self._center[0]
            yOffset = self._points[i][1] - self._center[1]

            self._points[i] = ( \
                self._center[0] + xOffset * cos(dTheta * 2*pi / 360) - yOffset * sin(dTheta * 2*pi / 360), \
                self._center[1] + xOffset * sin(dTheta * 2*pi / 360) + yOffset * cos(dTheta * 2*pi / 360))

        self._turtle.left(dTheta)

        self.redraw()

    def get_points(self):
        return self._points

class Rectangle(Shape):

    def __init__(self, \
        lowerLeftCorner: Tuple[int, int], width: int, height: int, \
        lineWidth: int = 1, \
        lineColor: Color = None, \
        fillColor: Color = None):

        """ The constructor for the Rectangle Class.

        In addition to the tasks performed by the constructor for the Shape
        class, this sets the lowerLeftCorner parameter of the shape, as well
        as calling the _determinePoints method to construct the 4 defining
        points of the rectangle.

        Args:
            lowerLeftCorner ((int, int)): A point denoting the bottom left corner of the rectangle
            width (int): The width of the rectangle
            height (int): The height of the rectangle
            lineWidth (int): The penwidth of the outer edge
            lineColor (Color): The pencolor of the outer edge
            fillColor (Color): The color used to fill the shape
        """

        Shape.__init__(self, lineWidth, lineColor, fillColor)

        self.lowerLeftCorner = lowerLeftCorner
        self.width = width
        self.height = height

        self._determinePoints()

    def _determinePoints(self):
        """ Assembles the 4 defining points of the Rectangle and saves them in _points. """

        self._points = [ \
            self.lowerLeftCorner, \
            (self.lowerLeftCorner[0] + self.width, self.lowerLeftCorner[1]), \
            (self.lowerLeftCorner[0] + self.width, self.lowerLeftCorner[1] + self.height), \
            (self.lowerLeftCorner[0], self.lowerLeftCorner[1] + self.height), \
            self.lowerLeftCorner]

        self._center = (self.lowerLeftCorner[0] + self.width/2, self.lowerLeftCorner[1] + self.height/2)

class Oval(Shape):

    def __init__(self, center: Tuple[float, float], width: int, height: int, \
        steps: int = 500, \
        lineWidth: int = 1, \
        lineColor: Color = None, \
        fillColor: Color = None):

        """ The constructor for the Oval Class.

        In addition to the tasks performed by the constructor for the Shape
        class, this sets the center of the shape, as well as calling the
        _determinePoints method to construct the points of the ellipse. The
        steps parameter specifies the number of points to use for the
        construction of the outline.

        Args:
            center ((float, float)): A point denoting the center of the oval
            width (float): The width of the oval
            height (float): The height of the oval
            steps (int): The number of points to create for the oval
            lineWidth (int): The line width of the outer edge
            lineColor (Color): The pencolor of the oute edge
            fillColor (Color): The color used to fill the given shape
        """

        Shape.__init__(self, lineWidth, lineColor, fillColor)

        self._center = center
        self._width = width
        self._height = height

        self._steps = steps
        self._determinePoints()

    def _determinePoints(self):
        """
        Populates _points with as many points as specified by
        steps via the parametric equations for an ellipse
        """

        for i in range(self._steps):
            self._points.append( \
                (self._center[0] + self._width * cos(i*2*pi/self._steps), \
                self._center[1] + self._height * sin(i*2*pi/self._steps)) \
                )

        self._points.append(self._points[0])

if __name__ == '__main__':
    def updateDirection():
        global direction, rotation

        x, y = list(zip(*oval.get_points()))

        if max(x) >= 200:
            direction[0] = -3
            rotation = -3 if direction[1] > 0 else 3
        elif min(x) <= -200:
            direction[0] = 3
            rotation = 3 if direction[1] > 0 else -3

        if max(y) >= 250:
            direction[1] = -3
            rotation = 3 if direction[0] > 0 else -3
        elif min(y) <= -250:
            direction[1] = 3
            rotation = -3 if direction[0] > 0 else 3

    def draw():
        oval.move(*direction)
        updateDirection()
        oval.rotate(rotation)
        screen.ontimer(draw, 1000//65)

    screen = Screen()
    window = Window("Tester", 600, 600)
    Rectangle((-200, -250), 400, 500, 5, fillColor='Green').draw()

    oval = Oval((-50, -50), 100, 50, 200, 3, 'Red', 'Blue')
    oval.draw()

    direction = [3, 3]
    rotation = 3

    draw()

    screen.mainloop()

看看這對您的用戶是否有任何影響。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM